diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevConstants.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevConstants.kt index b720b3a1ff8..b8678aead68 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevConstants.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevConstants.kt @@ -25,3 +25,16 @@ enum class ModifySourceFolderErrorReason( override fun toString(): String = reasonText } + +enum class FeatureDevOperation(private val operationName: String) { + StartTaskAssistCodeGeneration("StartTaskAssistCodeGenerator"), + CreateConversation("CreateConversation"), + CreateUploadUrl("CreateUploadUrl"), + GenerateCode("GenerateCode"), + GetTaskAssistCodeGeneration("GetTaskAssistCodeGenerator"), + ExportTaskAssistArchiveResult("ExportTaskAssistArchiveResult"), + UploadToS3("UploadToS3"), + ; + + override fun toString(): String = operationName +} diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt index 3cdbcf1c04f..412c35540e0 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevExceptions.kt @@ -6,40 +6,69 @@ package software.aws.toolkits.jetbrains.services.amazonqFeatureDev import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeError import software.aws.toolkits.resources.message -open class FeatureDevException(override val message: String?, override val cause: Throwable? = null) : RuntimeException() +/** + * FeatureDevException models failures from feature dev operations. + * + * - Each failure is annotated based on className, operation, and a short desc. Use the `reason()` and `reasonDesc()` members for instrumentation. + * - To throw an exception without modeling, throw FeatureDevException directly. + */ +open class FeatureDevException(override val message: String?, val operation: String, val desc: String?, override val cause: Throwable? = null) : + RuntimeException() { + fun reason(): String = this.javaClass.simpleName -class ContentLengthError(override val message: String, override val cause: Throwable?) : RepoSizeError, RuntimeException() + fun reasonDesc(): String = + when (desc) { + desc -> "$operation | Description: $desc" + else -> operation + } +} -class ZipFileError(override val message: String, override val cause: Throwable?) : RuntimeException() +class NoChangeRequiredException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.no_change_required_exception"), operation, desc, cause) -class CodeIterationLimitError(override val message: String, override val cause: Throwable?) : RuntimeException() +class EmptyPatchException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.guardrails"), operation, desc, cause) -class MonthlyConversationLimitError(override val message: String, override val cause: Throwable?) : RuntimeException() +class ContentLengthException( + override val message: String = message("amazonqFeatureDev.content_length.error_text"), + operation: String, + desc: String?, + cause: Throwable? = null, +) : + RepoSizeError, FeatureDevException(message, operation, desc, cause) -class UploadURLExpired( - override val message: String = message( - "amazonqFeatureDev.exception.upload_url_expiry" - ), - override val cause: Throwable? = null, -) : FeatureDevException(message, cause) +class ZipFileCorruptedException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException("The zip file is corrupted", operation, desc, cause) -internal fun featureDevServiceError(message: String?): Nothing = - throw FeatureDevException(message) +class UploadURLExpired(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.upload_url_expiry"), operation, desc, cause) -internal fun codeGenerationFailedError(): Nothing = - throw FeatureDevException(message("amazonqFeatureDev.code_generation.failed_generation")) +class CodeIterationLimitException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.code_generation.iteration_limit.error_text"), operation, desc, cause) -internal fun uploadCodeError(): Nothing = - throw FeatureDevException(message("amazonqFeatureDev.exception.upload_code")) +class MonthlyConversationLimitError(message: String, operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message, operation, desc, cause) -internal fun conversationIdNotFound(): Nothing = - throw FeatureDevException(message("amazonqFeatureDev.exception.conversation_not_found")) +class GuardrailsException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.guardrails"), operation, desc, cause) -internal fun apiError(message: String?, cause: Throwable?): Nothing = - throw FeatureDevException(message, cause) +class PromptRefusalException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.prompt_refusal"), operation, desc, cause) -internal fun exportParseError(): Nothing = - throw FeatureDevException(message("amazonqFeatureDev.exception.export_parsing_error")) +class ThrottlingException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.throttling"), operation, desc, cause) + +class ExportParseException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.export_parsing_error"), operation, desc, cause) + +class CodeGenerationException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.code_generation.failed_generation"), operation, desc, cause) + +class UploadCodeException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.upload_code"), operation, desc, cause) + +class ConversationIdNotFoundException(operation: String, desc: String?, cause: Throwable? = null) : + FeatureDevException(message("amazonqFeatureDev.exception.conversation_not_found"), operation, desc, cause) val denyListedErrors = arrayOf("Deserialization error", "Inaccessible host", "UnknownHost") fun createUserFacingErrorMessage(message: String?): String? = diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt index f2c37017c45..1e637767bbb 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt @@ -29,7 +29,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeError import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.DEFAULT_RETRY_LIMIT import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevException @@ -37,7 +37,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.InboundAppMess import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ModifySourceFolderErrorReason import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadURLExpired -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileCorruptedException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.createUserFacingErrorMessage import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.denyListedErrors import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.FeatureDevMessageType @@ -433,7 +433,7 @@ class FeatureDevController( ), ) } - is ZipFileError -> { + is ZipFileCorruptedException -> { messenger.sendError( tabId = tabId, errMessage = err.message, @@ -451,15 +451,7 @@ class FeatureDevController( messageType = FeatureDevMessageType.Answer, canBeVoted = true ) - is FeatureDevException -> { - messenger.sendError( - tabId = tabId, - errMessage = err.message, - retries = retriesRemaining(session), - conversationId = session?.conversationIdUnsafe - ) - } - is CodeIterationLimitError -> { + is CodeIterationLimitException -> { messenger.sendError( tabId = tabId, errMessage = err.message, @@ -479,24 +471,36 @@ class FeatureDevController( ) } else -> { - var msg = createUserFacingErrorMessage("$FEATURE_NAME request failed: ${err.message ?: err.cause?.message}") - val isDenyListedError = denyListedErrors.any { msg?.contains(it) ?: false } - val defaultMessage: String = when (session?.sessionState?.phase) { - SessionStatePhase.CODEGEN -> { - if (isDenyListedError || retriesRemaining(session) > 0) { - message("amazonqFeatureDev.code_generation.error_message") - } else { - message("amazonqFeatureDev.code_generation.no_retries.error_message") + when (err) { + is FeatureDevException -> { + messenger.sendError( + tabId = tabId, + errMessage = err.message, + retries = retriesRemaining(session), + conversationId = session?.conversationIdUnsafe + ) + } + else -> { + val msg = createUserFacingErrorMessage("$FEATURE_NAME request failed: ${err.message ?: err.cause?.message}") + val isDenyListedError = denyListedErrors.any { msg?.contains(it) ?: false } + val defaultMessage: String = when (session?.sessionState?.phase) { + SessionStatePhase.CODEGEN -> { + if (isDenyListedError || retriesRemaining(session) > 0) { + message("amazonqFeatureDev.code_generation.error_message") + } else { + message("amazonqFeatureDev.code_generation.no_retries.error_message") + } + } + else -> message("amazonqFeatureDev.error_text") } + messenger.sendError( + tabId = tabId, + errMessage = defaultMessage, + retries = retriesRemaining(session), + conversationId = session?.conversationIdUnsafe + ) } - else -> message("amazonqFeatureDev.error_text") } - messenger.sendError( - tabId = tabId, - errMessage = defaultMessage, - retries = retriesRemaining(session), - conversationId = session?.conversationIdUnsafe - ) } } } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/CodeGenerationState.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/CodeGenerationState.kt index 9bec70b83c3..3c1a9f665ad 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/CodeGenerationState.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/CodeGenerationState.kt @@ -8,9 +8,14 @@ import software.amazon.awssdk.services.codewhispererruntime.model.CodeGeneration import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeGenerationException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.EmptyPatchException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.codeGenerationFailedError -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.featureDevServiceError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevOperation +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.GuardrailsException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.PromptRefusalException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ThrottlingException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendAnswerPart import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendUpdatePlaceholder import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl @@ -37,6 +42,7 @@ class CodeGenerationState( val startTime = System.currentTimeMillis() var result: Result = Result.Succeeded var failureReason: String? = null + var failureReasonDesc: String? = null var codeGenerationWorkflowStatus: CodeGenerationWorkflowStatus = CodeGenerationWorkflowStatus.COMPLETE var numberOfReferencesGenerated: Int? = null var numberOfFilesGenerated: Int? = null @@ -86,6 +92,10 @@ class CodeGenerationState( logger.warn(e) { "$FEATURE_NAME: Code generation failed: ${e.message}" } result = Result.Failed failureReason = e.javaClass.simpleName + if (e is FeatureDevException) { + failureReason = e.reason() + failureReasonDesc = e.reasonDesc() + } codeGenerationWorkflowStatus = CodeGenerationWorkflowStatus.FAILED throw e @@ -100,6 +110,7 @@ class CodeGenerationState( amazonqRepositorySize = repositorySize, result = result, reason = failureReason, + reasonDesc = failureReasonDesc, duration = (System.currentTimeMillis() - startTime).toDouble(), credentialStartUrl = getStartUrl(config.featureDevService.project) ) @@ -149,20 +160,20 @@ private suspend fun CodeGenerationState.generateCode(codeGenerationId: String, m codeGenerationResultState.codeGenerationStatusDetail()?.contains( "Guardrails" ), - -> featureDevServiceError(message("amazonqFeatureDev.exception.guardrails")) + -> throw GuardrailsException(operation = FeatureDevOperation.GenerateCode.toString(), desc = "Failed guardrails") codeGenerationResultState.codeGenerationStatusDetail()?.contains( "PromptRefusal" ), - -> featureDevServiceError(message("amazonqFeatureDev.exception.prompt_refusal")) + -> throw PromptRefusalException(operation = FeatureDevOperation.GenerateCode.toString(), desc = "Prompt refusal") codeGenerationResultState.codeGenerationStatusDetail()?.contains( "EmptyPatch" ), - -> featureDevServiceError(message("amazonqFeatureDev.exception.guardrails")) + -> throw EmptyPatchException(operation = FeatureDevOperation.GenerateCode.toString(), desc = "Empty patch") codeGenerationResultState.codeGenerationStatusDetail()?.contains( "Throttling" ), - -> featureDevServiceError(message("amazonqFeatureDev.exception.throttling")) - else -> codeGenerationFailedError() + -> throw ThrottlingException(operation = FeatureDevOperation.GenerateCode.toString(), desc = "Request throttled") + else -> throw CodeGenerationException(operation = FeatureDevOperation.GenerateCode.toString(), desc = null) } } else -> error("Unknown status: ${codeGenerationResultState.codeGenerationStatus().status()}") diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/Session.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/Session.kt index b86cd4852af..bd47ab962b8 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/Session.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/Session.kt @@ -9,10 +9,10 @@ import com.intellij.openapi.vfs.VfsUtil import software.aws.toolkits.jetbrains.services.amazonq.FeatureDevSessionContext import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CODE_GENERATION_RETRY_LIMIT +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ConversationIdNotFoundException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MAX_PROJECT_SIZE_BYTES import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.clients.FeatureDevClient -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.conversationIdNotFound import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendAsyncEventProgress import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.FeatureDevService import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.resolveAndCreateOrUpdateFile @@ -138,7 +138,7 @@ class Session(val tabID: String, val project: Project) { val conversationId: String get() { if (_conversationId == null) { - conversationIdNotFound() + throw ConversationIdNotFoundException(operation = "Session", desc = "Conversation ID not found") } else { return _conversationId as String } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevService.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevService.kt index 661a40f4183..59a7750201f 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevService.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevService.kt @@ -17,18 +17,18 @@ import software.aws.toolkits.core.utils.debug import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitError -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ContentLengthError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ContentLengthException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ExportParseException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevOperation import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileError -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.apiError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileCorruptedException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.clients.FeatureDevClient -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.exportParseError import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.CodeGenerationStreamResult import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.ExportTaskAssistResultArchiveStreamResult import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl -import software.aws.toolkits.resources.message import software.aws.toolkits.telemetry.AmazonqTelemetry import software.aws.toolkits.telemetry.Result @@ -38,6 +38,7 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) fun createConversation(): String { val startTime = System.currentTimeMillis() var failureReason: String? = null + var failureReasonDesc: String? = null var result: Result = Result.Succeeded var conversationId: String? = null try { @@ -53,23 +54,28 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) return conversationId } catch (e: Exception) { logger.warn(e) { "$FEATURE_NAME: Failed to start conversation: ${e.message}" } - failureReason = e.javaClass.simpleName result = Result.Failed + failureReason = e.javaClass.simpleName + if (e is FeatureDevException) { + failureReason = e.reason() + failureReasonDesc = e.reasonDesc() + } var errMssg = e.message if (e is CodeWhispererRuntimeException) { errMssg = e.awsErrorDetails().errorMessage() logger.warn(e) { "Start conversation failed for request: ${e.requestId()}" } if (e is software.amazon.awssdk.services.codewhispererruntime.model.ServiceQuotaExceededException) { - throw MonthlyConversationLimitError(errMssg, e.cause) + throw MonthlyConversationLimitError(errMssg, operation = FeatureDevOperation.CreateConversation.toString(), desc = null, cause = e.cause) } } - apiError(errMssg, e.cause) + throw FeatureDevException(errMssg, operation = FeatureDevOperation.CreateConversation.toString(), desc = null, e.cause) } finally { AmazonqTelemetry.startConversationInvoke( amazonqConversationId = conversationId, result = result, reason = failureReason, + reasonDesc = failureReasonDesc, duration = (System.currentTimeMillis() - startTime).toDouble(), credentialStartUrl = getStartUrl(project = this.project), ) @@ -98,10 +104,10 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) logger.warn(e) { "Create UploadUrl failed for request: ${e.requestId()}" } if (e is ValidationException && e.message?.contains("Invalid contentLength") == true) { - throw ContentLengthError(message("amazonqFeatureDev.content_length.error_text"), e.cause) + throw ContentLengthException(operation = FeatureDevOperation.CreateUploadUrl.toString(), desc = null, cause = e.cause) } } - apiError(errMssg, e.cause) + throw FeatureDevException(errMssg, operation = FeatureDevOperation.CreateUploadUrl.toString(), desc = null, e.cause) } } @@ -131,14 +137,14 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) ) == true ) ) { - throw CodeIterationLimitError(message("amazonqFeatureDev.code_generation.iteration_limit.error_text"), e.cause) + throw CodeIterationLimitException(operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, e.cause) } else if (e is ValidationException && e.message?.contains("repo size is exceeding the limits") == true) { - throw ContentLengthError(message("amazonqFeatureDev.content_length.error_text"), e.cause) + throw ContentLengthException(operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, cause = e.cause) } else if (e is ValidationException && e.message?.contains("zipped file is corrupted") == true) { - throw ZipFileError("The zip file is corrupted", e.cause) + throw ZipFileCorruptedException(operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, e.cause) } } - apiError(errMssg, e.cause) + throw FeatureDevException(errMssg, operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, e.cause) } } @@ -162,7 +168,7 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) errMssg = e.awsErrorDetails().errorMessage() logger.warn(e) { "GetTaskAssistCodeGeneration failed for request: ${e.requestId()}" } } - apiError(errMssg, e.cause) + throw FeatureDevException(errMssg, operation = FeatureDevOperation.GetTaskAssistCodeGeneration.toString(), desc = null, e.cause) } } @@ -179,7 +185,7 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) errMssg = e.awsErrorDetails().errorMessage() logger.warn(e) { "ExportTaskAssistArchiveResult failed for request: ${e.requestId()}" } } - apiError(errMssg, e.cause) + throw FeatureDevException(errMssg, operation = FeatureDevOperation.ExportTaskAssistArchiveResult.toString(), desc = null, e.cause) } val parsedResult: ExportTaskAssistResultArchiveStreamResult @@ -188,7 +194,7 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project) parsedResult = jacksonObjectMapper().readValue(result) } catch (e: Exception) { logger.error(e) { "Failed to parse downloaded code results" } - exportParseError() + throw ExportParseException(operation = FeatureDevOperation.ExportTaskAssistArchiveResult.toString(), desc = null, e.cause) } return parsedResult.code_generation_result diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt index 66177d5a21f..cb435409320 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/UploadArtifact.kt @@ -14,9 +14,10 @@ import software.aws.toolkits.jetbrains.services.amazonq.CONTENT_SHA256 import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION import software.aws.toolkits.jetbrains.services.amazonq.SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevOperation +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadCodeException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.UploadURLExpired import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.clients.FeatureDevClient -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.uploadCodeError import java.io.File import java.net.HttpURLConnection @@ -39,10 +40,9 @@ fun uploadArtifactToS3(url: String, fileToUpload: File, checksumSha256: String, } } catch (err: HttpRequests.HttpStatusException) { logger.warn(err) { "$FEATURE_NAME: Failed to upload code to S3" } - when (err.statusCode) { - 403 -> throw UploadURLExpired() - else -> uploadCodeError() + 403 -> throw UploadURLExpired(operation = FeatureDevOperation.UploadToS3.toString(), desc = "Upload URL expired or forbidden") + else -> throw UploadCodeException(operation = FeatureDevOperation.UploadToS3.toString(), desc = "Failed to upload code to S3") } } } diff --git a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevServiceTest.kt b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevServiceTest.kt index bda0c3a9209..419258a350a 100644 --- a/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevServiceTest.kt +++ b/plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/util/FeatureDevServiceTest.kt @@ -19,8 +19,9 @@ import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUr import software.amazon.awssdk.services.codewhispererruntime.model.ServiceQuotaExceededException import software.amazon.awssdk.services.codewhispererruntime.model.ThrottlingException import software.amazon.awssdk.services.codewhispererruntime.model.ValidationException -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitError -import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ContentLengthError +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ContentLengthException +import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ExportParseException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevException import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevTestBase import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.clients.FeatureDevClient @@ -91,7 +92,7 @@ class FeatureDevServiceTest : FeatureDevTestBase() { assertThatThrownBy { featureDevService.createUploadUrl(testConversationId, testChecksumSha, testContentLength) - }.isInstanceOf(ContentLengthError::class.java).hasMessage(message("amazonqFeatureDev.content_length.error_text")) + }.isInstanceOf(ContentLengthException::class.java).hasMessage(message("amazonqFeatureDev.content_length.error_text")) } @Test @@ -145,7 +146,7 @@ class FeatureDevServiceTest : FeatureDevTestBase() { assertThatThrownBy { featureDevService.startTaskAssistCodeGeneration(testConversationId, testUploadId, userMessage) - }.isExactlyInstanceOf(CodeIterationLimitError::class.java).withFailMessage( + }.isExactlyInstanceOf(CodeIterationLimitException::class.java).withFailMessage( message("amazonqFeatureDev.code_generation.iteration_limit.error_text") ) } @@ -159,7 +160,7 @@ class FeatureDevServiceTest : FeatureDevTestBase() { assertThatThrownBy { featureDevService.startTaskAssistCodeGeneration(testConversationId, testUploadId, userMessage) - }.isExactlyInstanceOf(CodeIterationLimitError::class.java).withFailMessage( + }.isExactlyInstanceOf(CodeIterationLimitException::class.java).withFailMessage( message("amazonqFeatureDev.code_generation.iteration_limit.error_text") ) } @@ -219,7 +220,7 @@ class FeatureDevServiceTest : FeatureDevTestBase() { whenever(featureDevClient.exportTaskAssistResultArchive(testConversationId)).thenReturn(mutableListOf(byteArrayOf(0, 1, 2))) featureDevService.exportTaskAssistArchiveResult(testConversationId) } - }.isExactlyInstanceOf(FeatureDevException::class.java) + }.isExactlyInstanceOf(ExportParseException::class.java) } @Test 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 b575a7213df..41bf34b2467 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -66,6 +66,7 @@ amazonqFeatureDev.exception.export_parsing_error=I'm sorry, I'm having trouble p amazonqFeatureDev.exception.guardrails=I'm sorry, I'm having trouble generating your code. Please try again. amazonqFeatureDev.exception.insert_code_failed=Failed to insert code changes amazonqFeatureDev.exception.monthly_limit_error=You've reached the monthly quota for the Amazon Q agent for software development. You can try again next month. For more information on usage limits, see the [Amazon Q Developer pricing page](https://aws.amazon.com/q/developer/pricing/). +amazonqFeatureDev.exception.no_change_required_exception=I'm sorry, I ran into an issue while trying to generate your code.\n\n- `/dev` can generate code to make a change in your project. Provide a detailed description of the new feature or code changes you want to make, including the specifics of what the code should achieve.\n\n- To ask me to explain, debug, or optimize your code, you can close this chat tab to start a new conversation. amazonqFeatureDev.exception.open_diff_failed=Failed to open diff amazonqFeatureDev.exception.prompt_refusal=I'm sorry, I can't generate code for your request. Please make sure your message and code files comply with the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/). amazonqFeatureDev.exception.request_failed=Request failed