Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,42 @@ class FeatureDevClient(
requestBuilder.userContext(featureDevUserContext)
}

fun sendFeatureDevCodeGenerationEvent(
conversationId: String,
linesOfCodeGenerated: Int,
charactersOfCodeGenerated: Int,
): SendTelemetryEventResponse =
bearerClient().sendTelemetryEvent { requestBuilder ->
requestBuilder.telemetryEvent { telemetryEventBuilder ->
telemetryEventBuilder.featureDevCodeGenerationEvent {
it
.conversationId(conversationId)
.linesOfCodeGenerated(linesOfCodeGenerated)
.charactersOfCodeGenerated(charactersOfCodeGenerated)
}
}
requestBuilder.optOutPreference(getTelemetryOptOutPreference())
requestBuilder.userContext(featureDevUserContext)
}

fun sendFeatureDevCodeAcceptanceEvent(
conversationId: String,
linesOfCodeAccepted: Int,
charactersOfCodeAccepted: Int,
): SendTelemetryEventResponse =
bearerClient().sendTelemetryEvent { requestBuilder ->
requestBuilder.telemetryEvent { telemetryEventBuilder ->
telemetryEventBuilder.featureDevCodeAcceptanceEvent {
it
.conversationId(conversationId)
.linesOfCodeAccepted(linesOfCodeAccepted)
.charactersOfCodeAccepted(charactersOfCodeAccepted)
}
}
requestBuilder.optOutPreference(getTelemetryOptOutPreference())
requestBuilder.userContext(featureDevUserContext)
}

fun createTaskAssistConversation(): CreateTaskAssistConversationResponse =
bearerClient().createTaskAssistConversation(
CreateTaskAssistConversationRequest.builder().build(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ThrottlingExce
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendAnswerPart
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendUpdatePlaceholder
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.CancellationTokenSource
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.getChangeIdentifier
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.getDiffMetrics
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.readFileToString
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.AmazonqTelemetry
Expand All @@ -40,6 +43,7 @@ class CodeGenerationState(
override var codeGenerationTotalIterationCount: Int? = null,
var currentCodeGenerationId: String? = "EMPTY_CURRENT_CODE_GENERATION_ID",
override var token: CancellationTokenSource?,
override var diffMetricsProcessed: DiffMetricsProcessed,
) : SessionState {
override val phase = SessionStatePhase.CODEGEN

Expand Down Expand Up @@ -81,6 +85,41 @@ class CodeGenerationState(
codeGenerationRemainingIterationCount = codeGenerationResult.codeGenerationRemainingIterationCount
codeGenerationTotalIterationCount = codeGenerationResult.codeGenerationTotalIterationCount

runCatching {
var insertedLines = 0
var insertedCharacters = 0
codeGenerationResult.newFiles.forEach { file ->
// FIXME: Ideally, the before content should be read from the uploaded context instead of from disk, to avoid drift
val before = config.repoContext.selectedSourceFolder
.toNioPath()
.resolve(file.zipFilePath)
.toFile()
.let { f ->
if (f.exists() && f.canRead()) {
readFileToString(f)
} else {
""
}
}

val changeIdentifier = getChangeIdentifier(file.zipFilePath, before, file.fileContent)

if (!diffMetricsProcessed.generated.contains(changeIdentifier)) {
val diffMetrics = getDiffMetrics(before, file.fileContent)
insertedLines += diffMetrics.insertedLines
insertedCharacters += diffMetrics.insertedCharacters
diffMetricsProcessed.generated.add(changeIdentifier)
}
}
if (insertedLines > 0) {
config.featureDevService.sendFeatureDevCodeGenerationEvent(
conversationId = config.conversationId,
linesOfCodeGenerated = insertedLines,
charactersOfCodeGenerated = insertedCharacters,
)
}
}.onFailure { /* Noop on diff telemetry failure */ }

val nextState =
PrepareCodeGenerationState(
tabID = tabID,
Expand All @@ -95,6 +134,7 @@ class CodeGenerationState(
codeGenerationRemainingIterationCount = codeGenerationRemainingIterationCount,
codeGenerationTotalIterationCount = codeGenerationTotalIterationCount,
token = this.token,
diffMetricsProcessed = diffMetricsProcessed,
)

// It is not needed to interact right away with the PrepareCodeGeneration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ConversationNotStartedState(
override var codeGenerationRemainingIterationCount: Int?,
override var codeGenerationTotalIterationCount: Int?,
override var currentIteration: Int?,
override var diffMetricsProcessed: DiffMetricsProcessed,
) : SessionState {
override val phase = SessionStatePhase.INIT

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class PrepareCodeGenerationState(
private var messenger: MessagePublisher,
override var codeGenerationRemainingIterationCount: Int? = null,
override var codeGenerationTotalIterationCount: Int? = null,
override var diffMetricsProcessed: DiffMetricsProcessed,
) : SessionState {
override val phase = SessionStatePhase.CODEGEN
override suspend fun interact(action: SessionStateAction): SessionStateInteraction {
Expand Down Expand Up @@ -74,7 +75,8 @@ class PrepareCodeGenerationState(
currentIteration = this.currentIteration,
repositorySize = zipFileLength.toDouble(),
messenger = messenger,
token = this.token
token = this.token,
diffMetricsProcessed = diffMetricsProcessed
)
} catch (e: Exception) {
result = Result.Failed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendA
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.updateFileComponent
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.CancellationTokenSource
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.FeatureDevService
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.getChangeIdentifier
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.getDiffMetrics
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.readFileToString
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.resolveAndCreateOrUpdateFile
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.resolveAndDeleteFile
import software.aws.toolkits.jetbrains.services.cwc.controller.ReferenceLogController
import java.util.HashSet

class Session(val tabID: String, val project: Project) {
var context: FeatureDevSessionContext
Expand All @@ -45,7 +49,15 @@ class Session(val tabID: String, val project: Project) {
context = FeatureDevSessionContext(project, MAX_PROJECT_SIZE_BYTES)
proxyClient = FeatureDevClient.getInstance(project)
featureDevService = FeatureDevService(proxyClient, project)
_state = ConversationNotStartedState("", tabID, null, 0, CODE_GENERATION_RETRY_LIMIT, 0)
_state = ConversationNotStartedState(
approach = "",
tabID = tabID,
token = null,
codeGenerationRemainingIterationCount = 0,
codeGenerationTotalIterationCount = CODE_GENERATION_RETRY_LIMIT,
currentIteration = 0,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet())
)
isAuthenticating = false
codegenRetries = CODE_GENERATION_RETRY_LIMIT
}
Expand Down Expand Up @@ -86,6 +98,7 @@ class Session(val tabID: String, val project: Project) {
uploadId = "", // There is no code gen uploadId so far
messenger = messenger,
token = CancellationTokenSource(),
diffMetricsProcessed = sessionState.diffMetricsProcessed,
)
}

Expand All @@ -110,6 +123,42 @@ class Session(val tabID: String, val project: Project) {
val selectedSourceFolder = context.selectedSourceFolder.toNioPath()
val newFilePaths = filePaths.filter { !it.rejected && !it.changeApplied }
val newDeletedFiles = deletedFiles.filter { !it.rejected && !it.changeApplied }

runCatching {
var insertedLines = 0
var insertedCharacters = 0
filePaths.forEach { file ->
// FIXME: Ideally, the before content should be read from the uploaded context instead of from disk, to avoid drift
val before = selectedSourceFolder
.resolve(file.zipFilePath)
.toFile()
.let { f ->
if (f.exists() && f.canRead()) {
readFileToString(f)
} else {
""
}
}

val changeIdentifier = getChangeIdentifier(file.zipFilePath, before, file.fileContent)

if (_state?.diffMetricsProcessed?.accepted?.contains(changeIdentifier) != true) {
val diffMetrics = getDiffMetrics(before, file.fileContent)
insertedLines += diffMetrics.insertedLines
insertedCharacters += diffMetrics.insertedCharacters
_state?.diffMetricsProcessed?.accepted?.add(changeIdentifier)
}
}

if (insertedLines > 0) {
featureDevService.sendFeatureDevCodeAcceptanceEvent(
conversationId = conversationId,
linesOfCodeAccepted = insertedLines,
charactersOfCodeAccepted = insertedCharacters,
)
}
}.onFailure { /* Noop on diff telemetry failure */ }

newFilePaths.forEach {
resolveAndCreateOrUpdateFile(selectedSourceFolder, it.zipFilePath, it.fileContent)
it.changeApplied = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ interface SessionState {
var codeGenerationTotalIterationCount: Int?
var currentIteration: Int?
var approach: String
var diffMetricsProcessed: DiffMetricsProcessed
suspend fun interact(action: SessionStateAction): SessionStateInteraction
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,8 @@ data class CodeGenerationStreamResult(
data class ExportTaskAssistResultArchiveStreamResult(
var code_generation_result: CodeGenerationStreamResult,
)

data class DiffMetricsProcessed(
var accepted: HashSet<String>,
var generated: HashSet<String>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import com.intellij.diff.comparison.ComparisonManager
import com.intellij.diff.comparison.ComparisonPolicy
import com.intellij.diff.fragments.LineFragment
import com.intellij.openapi.progress.EmptyProgressIndicator
import io.ktor.utils.io.core.toByteArray
import java.nio.charset.Charset
import java.security.MessageDigest

data class DiffMetrics(
val insertedLines: Int,
Expand Down Expand Up @@ -37,7 +40,7 @@ fun getDiffMetrics(before: String, after: String): DiffMetrics {
val fragments = comparisonManager.compareLines(
before,
after,
ComparisonPolicy.DEFAULT,
ComparisonPolicy.IGNORE_WHITESPACES,
EmptyProgressIndicator()
)

Expand Down Expand Up @@ -71,3 +74,11 @@ fun getDiffMetrics(before: String, after: String): DiffMetrics {
insertedCharacters = accCharCount,
)
}

fun getChangeIdentifier(filePath: String, before: String, after: String): String {
val hash = MessageDigest.getInstance("SHA-1")
hash.update(filePath.toByteArray(Charset.forName("UTF-8")))
hash.update(before.toByteArray(Charset.forName("UTF-8")))
hash.update(after.toByteArray(Charset.forName("UTF-8")))
return hash.digest().joinToString("") { "%02x".format(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,32 @@ class FeatureDevService(val proxyClient: FeatureDevClient, val project: Project)
logger.warn(e) { "$FEATURE_NAME: failed to send feature dev telemetry" }
}
}

fun sendFeatureDevCodeGenerationEvent(conversationId: String, linesOfCodeGenerated: Int, charactersOfCodeGenerated: Int) {
val sendFeatureDevTelemetryEventResponse: SendTelemetryEventResponse
try {
sendFeatureDevTelemetryEventResponse = proxyClient
.sendFeatureDevCodeGenerationEvent(conversationId, linesOfCodeGenerated, charactersOfCodeGenerated)
val requestId = sendFeatureDevTelemetryEventResponse.responseMetadata().requestId()
logger.debug {
"$FEATURE_NAME: successfully sent feature dev code generation telemetry: ConversationId: $conversationId RequestId: $requestId"
}
} catch (e: Exception) {
logger.warn(e) { "$FEATURE_NAME: failed to send feature dev code generation telemetry" }
}
}

fun sendFeatureDevCodeAcceptanceEvent(conversationId: String, linesOfCodeAccepted: Int, charactersOfCodeAccepted: Int) {
val sendFeatureDevTelemetryEventResponse: SendTelemetryEventResponse
try {
sendFeatureDevTelemetryEventResponse = proxyClient
.sendFeatureDevCodeAcceptanceEvent(conversationId, linesOfCodeAccepted, charactersOfCodeAccepted)
val requestId = sendFeatureDevTelemetryEventResponse.responseMetadata().requestId()
logger.debug {
"$FEATURE_NAME: successfully sent feature dev code acceptance telemetry: ConversationId: $conversationId RequestId: $requestId"
}
} catch (e: Exception) {
logger.warn(e) { "$FEATURE_NAME: failed to send feature dev code acceptance telemetry" }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ package software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util
import com.intellij.openapi.fileChooser.FileChooser
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.VirtualFile
import java.io.File
import java.nio.charset.Charset
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
Expand All @@ -33,3 +36,9 @@ fun selectFolder(project: Project, openOn: VirtualFile): VirtualFile? {
val fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor()
return FileChooser.chooseFile(fileChooserDescriptor, project, openOn)
}

fun readFileToString(file: File): String {
val charsetToolkit = CharsetToolkit(file.readBytes(), Charset.forName("UTF-8"), false)
val charset = charsetToolkit.guessEncoding(4096)
return file.readText(charset)
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendS
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendUpdatePlaceholder
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.updateFileComponent
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.DeletedFileInfo
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.DiffMetricsProcessed
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.Interaction
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.NewFileZipInfo
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.PrepareCodeGenerationState
Expand Down Expand Up @@ -236,6 +237,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
messenger,
0,
0,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)

Expand Down Expand Up @@ -295,6 +297,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
testUploadId,
0,
messenger,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)

Expand Down Expand Up @@ -354,6 +357,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
testUploadId,
0,
messenger,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)
whenever(mockSession.retries).thenReturn(3)
Expand Down Expand Up @@ -393,6 +397,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
testUploadId,
0,
messenger,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)
whenever(mockSession.retries).thenReturn(0)
Expand Down Expand Up @@ -425,6 +430,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
testUploadId,
0,
messenger,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)

Expand Down Expand Up @@ -458,6 +464,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
testUploadId,
0,
messenger,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)
doReturn(testConversationId).`when`(spySession).conversationId
Expand Down Expand Up @@ -514,6 +521,7 @@ class FeatureDevControllerTest : FeatureDevTestBase() {
testUploadId,
0,
messenger,
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
),
)
doReturn(testConversationId).`when`(spySession).conversationId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class CodeGenerationStateTest : FeatureDevTestBase() {
messenger,
token = null,
currentCodeGenerationId = "EMPTY_CURRENT_CODE_GENERATION_ID",
diffMetricsProcessed = DiffMetricsProcessed(HashSet(), HashSet()),
)

mockkStatic(MessagePublisher::sendAnswerPart)
Expand Down
Loading
Loading