Skip to content

Commit debacc6

Browse files
committed
feat(amazonq): skip registering run command log file
1 parent 48b30c0 commit debacc6

File tree

4 files changed

+110
-9
lines changed

4 files changed

+110
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "The logs emitted by the Agent during user command execution will be accepted and written to `.amazonq/dev/run_command.log` file in the user's local repository."
4+
}

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

+38-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session
55

6+
import com.intellij.openapi.vfs.VirtualFile
67
import kotlinx.coroutines.delay
78
import software.amazon.awssdk.services.codewhispererruntime.model.CodeGenerationWorkflowStatus
89
import software.aws.toolkits.core.utils.getLogger
@@ -27,8 +28,13 @@ import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.ge
2728
import software.aws.toolkits.resources.message
2829
import software.aws.toolkits.telemetry.AmazonqTelemetry
2930
import software.aws.toolkits.telemetry.MetricResult
31+
import java.nio.charset.StandardCharsets
32+
import java.nio.file.Files
33+
import java.nio.file.Path
34+
import java.nio.file.Paths
3035
import java.util.UUID
3136

37+
fun VirtualFile.toNioPath(): Path = Paths.get(this.path)
3238
private val logger = getLogger<CodeGenerationState>()
3339

3440
class CodeGenerationState(
@@ -181,6 +187,20 @@ class CodeGenerationState(
181187
}
182188
}
183189

190+
private fun writeLogFile(addressableRoot: Any?, fileRelativePath: String, contents: String) {
191+
val addressablePath = when (addressableRoot) {
192+
is VirtualFile -> addressableRoot.toNioPath()
193+
is Path -> addressableRoot
194+
else -> Paths.get(System.getProperty("java.io.tmpdir"))
195+
}
196+
val filePath = addressablePath.resolve(fileRelativePath)
197+
198+
// Ensure parent directories exist
199+
Files.createDirectories(filePath.parent)
200+
// This call replaces the need to manually create and check the file.
201+
Files.write(filePath, contents.toByteArray(StandardCharsets.UTF_8))
202+
}
203+
184204
private suspend fun CodeGenerationState.generateCode(
185205
codeGenerationId: String,
186206
messenger: MessagePublisher,
@@ -211,6 +231,13 @@ private suspend fun CodeGenerationState.generateCode(
211231
conversationId = config.conversationId,
212232
)
213233

234+
for (file in codeGenerationStreamResult.new_file_contents.keys) {
235+
if (file.endsWith(".amazonq/dev/run_command.log")) {
236+
val contents = codeGenerationStreamResult.new_file_contents[file].orEmpty()
237+
writeLogFile(config.repoContext.addressableRoot, file, contents)
238+
}
239+
}
240+
214241
val newFileInfo = registerNewFiles(newFileContents = codeGenerationStreamResult.new_file_contents)
215242
val deletedFileInfo = registerDeletedFiles(deletedFiles = codeGenerationStreamResult.deleted_files)
216243

@@ -267,14 +294,17 @@ private suspend fun CodeGenerationState.generateCode(
267294
}
268295

269296
fun registerNewFiles(newFileContents: Map<String, String>): List<NewFileZipInfo> =
270-
newFileContents.map {
271-
NewFileZipInfo(
272-
// Note: When managing file state, we normalize file paths returned from the agent in order to ensure they are handled as relative paths.
273-
zipFilePath = it.key.removePrefix("/"),
274-
fileContent = it.value,
275-
rejected = false,
276-
changeApplied = false
277-
)
297+
newFileContents.mapNotNull { (key, value) ->
298+
if (key.endsWith(".amazonq/dev/run_command.log")) {
299+
null
300+
} else {
301+
NewFileZipInfo(
302+
zipFilePath = key.removePrefix("/"),
303+
fileContent = value,
304+
rejected = false,
305+
changeApplied = false
306+
)
307+
}
278308
}
279309

280310
fun registerDeletedFiles(deletedFiles: List<String>): List<DeletedFileInfo> =

plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevTestBase.kt

+2
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ open class FeatureDevTestBase(
6565
internal val otherStatus = "Other"
6666
internal val testTabId = "test-tab-id"
6767
internal val testFilePaths = mapOf(Pair("test.ts", "This is a comment"))
68+
internal val testRunCommandLogPath = ".amazonq/dev/run_command.log"
69+
internal val testLogPath = mapOf(Pair(testRunCommandLogPath, "This is a log"))
6870
internal val testDeletedFiles = listOf("deleted.ts")
6971
internal val testReferences = listOf(CodeReferenceGenerated())
7072
internal val testChecksumSha = "test-sha"

plugins/amazonq/chat/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/CodeGenerationStateTest.kt

+66-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session
55

6+
import com.intellij.openapi.vfs.VirtualFile
67
import com.intellij.testFramework.RuleChain
78
import io.mockk.coEvery
89
import io.mockk.coVerify
@@ -11,6 +12,7 @@ import io.mockk.just
1112
import io.mockk.mockk
1213
import io.mockk.mockkStatic
1314
import io.mockk.runs
15+
import io.mockk.slot
1416
import io.mockk.unmockkAll
1517
import io.mockk.verify
1618
import kotlinx.coroutines.test.runTest
@@ -29,6 +31,8 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.messages.sendA
2931
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.FeatureDevService
3032
import software.aws.toolkits.jetbrains.services.cwc.messages.CodeReference
3133
import software.aws.toolkits.resources.message
34+
import java.nio.file.Files
35+
import java.nio.file.Paths
3236

3337
class CodeGenerationStateTest : FeatureDevTestBase() {
3438
@Rule
@@ -39,13 +43,14 @@ class CodeGenerationStateTest : FeatureDevTestBase() {
3943
private lateinit var messenger: MessagePublisher
4044
private val action = SessionStateAction("test-task", userMessage)
4145
private lateinit var featureDevService: FeatureDevService
46+
private lateinit var repoContext: FeatureDevSessionContext
4247

4348
@Before
4449
override fun setup() {
4550
featureDevService = mockk<FeatureDevService>()
4651
every { featureDevService.project } returns projectRule.project
4752
messenger = mock()
48-
val repoContext = mock<FeatureDevSessionContext>()
53+
repoContext = mockk<FeatureDevSessionContext>()
4954
val sessionStateConfig = SessionStateConfig(testConversationId, repoContext, featureDevService)
5055

5156
codeGenerationState =
@@ -103,6 +108,66 @@ class CodeGenerationStateTest : FeatureDevTestBase() {
103108
coVerify(exactly = 1) { featureDevService.exportTaskAssistArchiveResult(testConversationId) }
104109
}
105110

111+
@Test
112+
fun `test code generated is complete with run_command log contents verification`() {
113+
val action = SessionStateAction("test-task", userMessage)
114+
115+
val tempDir = Files.createTempDirectory("testRepo")
116+
val mockedFile = mockk<VirtualFile>()
117+
every { mockedFile.exists() } returns true
118+
every { mockedFile.isDirectory } returns true
119+
every { mockedFile.path } returns tempDir.toAbsolutePath().toString()
120+
every { mockedFile.toNioPath() } returns tempDir
121+
122+
val pathSlot = slot<String>()
123+
every { mockedFile.findFileByRelativePath(capture(pathSlot)) } answers {
124+
val path = pathSlot.captured
125+
val filePath = tempDir.resolve(path)
126+
if (!Files.exists(filePath)) {
127+
Files.createFile(filePath)
128+
}
129+
val file = mockk<VirtualFile>()
130+
every { file.exists() } returns true
131+
every { file.isDirectory } returns Files.isDirectory(filePath)
132+
every { file.path } returns filePath.toAbsolutePath().toString()
133+
file
134+
}
135+
136+
every { repoContext.addressableRoot } returns mockedFile
137+
138+
every { featureDevService.getTaskAssistCodeGeneration(any(), any()) } returns exampleCompleteGetTaskAssistCodeGenerationResponse
139+
every { featureDevService.startTaskAssistCodeGeneration(any(), any(), any(), any(), any()) } returns exampleStartTaskAssistConversationResponse
140+
coEvery { featureDevService.exportTaskAssistArchiveResult(any()) } returns
141+
CodeGenerationStreamResult(testLogPath, testDeletedFiles, testReferences)
142+
143+
runTest {
144+
val actual = codeGenerationState.interact(action)
145+
assertThat(actual.nextState).isInstanceOf(PrepareCodeGenerationState::class.java)
146+
val nextState = actual.nextState as PrepareCodeGenerationState
147+
assertThat(nextState.phase).isEqualTo(SessionStatePhase.CODEGEN)
148+
assertThat(nextState.filePaths).doesNotContain(
149+
NewFileZipInfo(testRunCommandLogPath, "This is a log", rejected = false, changeApplied = false)
150+
)
151+
assertThat(nextState.filePaths.size).isEqualTo(0)
152+
assertThat(nextState.deletedFiles).isEqualTo(
153+
listOf(DeletedFileInfo("deleted.ts", rejected = false, changeApplied = false))
154+
)
155+
assertThat(nextState.references).isEqualTo(testReferences)
156+
assertThat(nextState.codeGenerationRemainingIterationCount).isEqualTo(2)
157+
assertThat(nextState.codeGenerationTotalIterationCount).isEqualTo(3)
158+
assertThat(actual.interaction.interactionSucceeded).isEqualTo(true)
159+
assertThat(actual.interaction.content).isEqualTo("")
160+
assertThat(mockedFile.findFileByRelativePath(testRunCommandLogPath)).isNotNull
161+
val file = requireNotNull(mockedFile.findFileByRelativePath(testRunCommandLogPath)) { "File not found" }
162+
assertThat(Files.readString(Paths.get(file.path))).isEqualTo("This is a log")
163+
}
164+
165+
assertThat(codeGenerationState.phase).isEqualTo(SessionStatePhase.CODEGEN)
166+
coVerify(exactly = 1) { messenger.sendAnswerPart(testTabId, message("amazonqFeatureDev.code_generation.generating_code")) }
167+
verify(exactly = 1) { featureDevService.getTaskAssistCodeGeneration(testConversationId, codeGenerationId) }
168+
coVerify(exactly = 1) { featureDevService.exportTaskAssistArchiveResult(testConversationId) }
169+
}
170+
106171
@Test(expected = FeatureDevException::class)
107172
fun `test code generation failed`() =
108173
runTest {

0 commit comments

Comments
 (0)