Skip to content

Commit ca86d62

Browse files
Merge branch 'feature/q-lsp-chat' into samgst/q-lsp-chat-stop-button
2 parents caa130a + 5f44d67 commit ca86d62

File tree

9 files changed

+154
-31
lines changed

9 files changed

+154
-31
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
7777
val name = telemetryMap["name"] as? String ?: return
7878

7979
@Suppress("UNCHECKED_CAST")
80-
val data = telemetryMap["data"] as? Map<String, Any> ?: return
80+
val data = telemetryMap["data"] as? Map<String, Any?> ?: return
8181

8282
TelemetryService.getInstance().record(project) {
8383
datum(name) {
@@ -90,7 +90,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
9090
}
9191

9292
data.forEach { (key, value) ->
93-
metadata(key, value.toString())
93+
metadata(key, value?.toString() ?: "null")
9494
}
9595
}
9696
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -372,35 +372,35 @@ private class AmazonQServerInstance(private val project: Project, private val cs
372372
}
373373

374374
val node = if (SystemInfo.isWindows) "node.exe" else "node"
375-
val cmd = GeneralCommandLine(
376-
artifact.resolve(node).toString(),
377-
LspSettings.getInstance().getArtifactPath() ?: artifact.resolve("aws-lsp-codewhisperer.js").toString(),
378-
"--stdio",
379-
"--set-credentials-encryption-key",
380-
).withEnvironment(
381-
buildMap {
382-
put("NODE_EXTRA_CA_CERTS", extraCaCerts.toAbsolutePath().toString())
383-
384-
val proxy = JdkProxyProvider.getInstance().proxySelector.select(qUri)
385-
// log if only socks proxy available
386-
.firstOrNull { it.type() == Proxy.Type.HTTP }
387-
388-
if (proxy != null) {
389-
val address = proxy.address()
390-
if (address is java.net.InetSocketAddress) {
391-
put(
392-
"HTTPS_PROXY",
393-
URIBuilder("http://${address.hostName}:${address.port}").apply {
394-
val login = HttpConfigurable.getInstance().proxyLogin
395-
if (login != null) {
396-
setUserInfo(login, HttpConfigurable.getInstance().plainProxyPassword)
397-
}
398-
}.build().toASCIIString()
399-
)
375+
val cmd = NodeExePatcher.patch(artifact.resolve(node))
376+
.withParameters(
377+
LspSettings.getInstance().getArtifactPath() ?: artifact.resolve("aws-lsp-codewhisperer.js").toString(),
378+
"--stdio",
379+
"--set-credentials-encryption-key",
380+
).withEnvironment(
381+
buildMap {
382+
put("NODE_EXTRA_CA_CERTS", extraCaCerts.toAbsolutePath().toString())
383+
384+
val proxy = JdkProxyProvider.getInstance().proxySelector.select(qUri)
385+
// log if only socks proxy available
386+
.firstOrNull { it.type() == Proxy.Type.HTTP }
387+
388+
if (proxy != null) {
389+
val address = proxy.address()
390+
if (address is java.net.InetSocketAddress) {
391+
put(
392+
"HTTPS_PROXY",
393+
URIBuilder("http://${address.hostName}:${address.port}").apply {
394+
val login = HttpConfigurable.getInstance().proxyLogin
395+
if (login != null) {
396+
setUserInfo(login, HttpConfigurable.getInstance().plainProxyPassword)
397+
}
398+
}.build().toASCIIString()
399+
)
400+
}
400401
}
401402
}
402-
}
403-
)
403+
)
404404
.withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE)
405405

406406
launcherHandler = KillableColoredProcessHandler.Silent(cmd)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp
5+
6+
import com.intellij.execution.configurations.GeneralCommandLine
7+
import software.aws.toolkits.core.utils.getLogger
8+
import software.aws.toolkits.core.utils.info
9+
import java.nio.file.Path
10+
11+
/**
12+
* Hacky nonsense to support old glibc platforms like AL2
13+
* @see "https://github.com/microsoft/vscode/issues/231623"
14+
* @see "https://github.com/aws/aws-toolkit-vscode/commit/6081f890bdbb91fcd8b60c4cc0abb65b15d4a38d"
15+
*/
16+
object NodeExePatcher {
17+
const val GLIBC_LINKER_VAR = "VSCODE_SERVER_CUSTOM_GLIBC_LINKER"
18+
const val GLIBC_PATH_VAR = "VSCODE_SERVER_CUSTOM_GLIBC_PATH"
19+
20+
fun patch(node: Path): GeneralCommandLine {
21+
val linker = System.getenv(GLIBC_LINKER_VAR)
22+
val glibc = System.getenv(GLIBC_PATH_VAR)
23+
val nodePath = node.toAbsolutePath().toString()
24+
25+
return if (!linker.isNullOrEmpty() && !glibc.isNullOrEmpty()) {
26+
GeneralCommandLine(linker)
27+
.withParameters("--library-path", glibc, nodePath)
28+
.also {
29+
getLogger<NodeExePatcher>().info { "Using glibc patch: $it" }
30+
}
31+
} else {
32+
GeneralCommandLine(nodePath)
33+
}
34+
}
35+
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH,
7171
return localFolders
7272
.mapNotNull { localFolder ->
7373
SemVer.parseFromText(localFolder.fileName.toString())?.let { semVer ->
74-
if (semVer in manifestVersionRanges.startVersion..manifestVersionRanges.endVersion) {
74+
if (semVer >= manifestVersionRanges.startVersion && semVer < manifestVersionRanges.endVersion) {
7575
localFolder to semVer
7676
} else {
7777
null

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ArtifactManager @NonInjectable internal constructor(private val manifestFe
100100
SemVer.parseFromText(serverVersion)?.let { semVer ->
101101
when {
102102
version.isDelisted != false -> Pair(version, true) // Is deListed
103-
semVer in DEFAULT_VERSION_RANGE.let { it.startVersion..it.endVersion } -> Pair(version, false) // Is in range
103+
(semVer >= DEFAULT_VERSION_RANGE.startVersion && semVer < DEFAULT_VERSION_RANGE.endVersion) -> Pair(version, false) // Is in range
104104
else -> null
105105
}
106106
}

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImplTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,36 @@ class AmazonQLanguageClientImplTest {
8181
)
8282
}
8383

84+
@Test
85+
fun `telemetryEvent handles null`() {
86+
val telemetryService = mockk<TelemetryService>(relaxed = true)
87+
mockkObject(TelemetryService)
88+
every { TelemetryService.getInstance() } returns telemetryService
89+
90+
val builderCaptor = slot<MetricEvent.Builder.() -> Unit>()
91+
every { telemetryService.record(project, capture(builderCaptor)) } returns Unit
92+
93+
val event = mapOf(
94+
"name" to "test_event",
95+
"data" to mapOf(
96+
"key1" to null,
97+
)
98+
)
99+
100+
sut.telemetryEvent(event)
101+
102+
val builder = DefaultMetricEvent.builder()
103+
builderCaptor.captured.invoke(builder)
104+
105+
val metricEvent = builder.build()
106+
val datum = metricEvent.data.first()
107+
108+
assertThat(datum.name).isEqualTo("test_event")
109+
assertThat(datum.metadata).contains(
110+
entry("key1", "null"),
111+
)
112+
}
113+
84114
@Test
85115
fun `telemetryEvent handles event with result field`() {
86116
val telemetryService = mockk<TelemetryService>(relaxed = true)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp
5+
6+
import com.intellij.execution.configurations.GeneralCommandLine
7+
import org.assertj.core.api.Assertions.assertThat
8+
import org.junit.Rule
9+
import org.junit.Test
10+
import software.aws.toolkits.core.rules.EnvironmentVariableHelper
11+
import kotlin.io.path.Path
12+
13+
class NodeExePatcherTest {
14+
@get:Rule
15+
val envVarHelper = EnvironmentVariableHelper()
16+
17+
private val pathToNode = Path("/path/to/node").toAbsolutePath().toString()
18+
19+
@Test
20+
fun `patches if path available`() {
21+
envVarHelper[NodeExePatcher.GLIBC_LINKER_VAR] = "/opt/vsc-sysroot/lib/ld-linux-x86-64.so.2"
22+
envVarHelper[NodeExePatcher.GLIBC_PATH_VAR] = "/opt/vsc-sysroot/lib/"
23+
24+
assertThat(NodeExePatcher.patch(Path("/path/to/node")))
25+
.usingComparator(Comparator.comparing { it.commandLineString })
26+
.isEqualTo(GeneralCommandLine("/opt/vsc-sysroot/lib/ld-linux-x86-64.so.2", "--library-path", "/opt/vsc-sysroot/lib/", pathToNode))
27+
}
28+
29+
@Test
30+
fun `noop if no patch available`() {
31+
assertThat(NodeExePatcher.patch(Path("/path/to/node")))
32+
.usingComparator(Comparator.comparing { it.commandLineString })
33+
.isEqualTo(GeneralCommandLine(pathToNode))
34+
}
35+
}

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelperTest.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class ArtifactHelperTest {
101101
tempDir.resolve("1.0.0").createDirectories()
102102
tempDir.resolve("1.0.1").createDirectories()
103103
tempDir.resolve("1.0.2").createDirectories()
104+
104105
manifestVersionRanges = SupportedManifestVersionRange(
105106
startVersion = SemVer("1.0.0", 1, 0, 0),
106107
endVersion = SemVer("2.0.0", 2, 0, 0)
@@ -112,6 +113,21 @@ class ArtifactHelperTest {
112113
assertThat(actualResult.first().first.fileName.toString()).isEqualTo("1.0.2")
113114
}
114115

116+
@Test
117+
fun `getAllLocalLspArtifactsWithinManifestRange does not return end major version path`() {
118+
tempDir.resolve("1.0.0").createDirectories()
119+
tempDir.resolve("2.0.0").createDirectories()
120+
manifestVersionRanges = SupportedManifestVersionRange(
121+
startVersion = SemVer("1.0.0", 1, 0, 0),
122+
endVersion = SemVer("2.0.0", 2, 0, 0)
123+
)
124+
125+
val actualResult = artifactHelper.getAllLocalLspArtifactsWithinManifestRange(manifestVersionRanges)
126+
assertThat(actualResult).isNotNull()
127+
assertThat(actualResult.size).isEqualTo(1)
128+
assertThat(actualResult.first().first.fileName.toString()).isNotEqualTo("2.0.0")
129+
}
130+
115131
@Test
116132
fun `getExistingLspArtifacts should find all the artifacts`() {
117133
val version1Dir = tempDir.resolve("1.0.0").apply { toFile().mkdirs() }

plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManagerTest.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ class ArtifactManagerTest {
7676
.hasFieldOrPropertyWithValue("errorCode", LspException.ErrorCode.NO_COMPATIBLE_LSP_VERSION)
7777
}
7878

79+
@Test
80+
fun `getLSPVersionsFromManifestWithSpecifiedRange excludes end major version`() = runTest {
81+
val newManifest = Manifest(versions = listOf(Version(serverVersion = "2.0.0")))
82+
val result = artifactManager.getLSPVersionsFromManifestWithSpecifiedRange(newManifest)
83+
assertThat(result.inRangeVersions).isEmpty()
84+
}
85+
7986
@Test
8087
fun `fetch artifact if inRangeVersions are not available should fallback to local lsp`() = runTest {
8188
val expectedResult = listOf(Pair(tempDir, SemVer("1.0.0", 1, 0, 0)))

0 commit comments

Comments
 (0)