From 925ba92dbf03330c3c8584af1ed1112b040f6f1b Mon Sep 17 00:00:00 2001 From: Richard Li Date: Wed, 9 Apr 2025 10:26:55 -0700 Subject: [PATCH 1/2] fix(amazonq): make amazon.q.endpoints.json more maintainable We do not need to stuff the prod configuration into the plugin.xml. Additionally we should gracefully handle invalid configuration instead of giving up --- .../util/CodeWhispererEndpointCustomizer.kt | 3 +- .../services/codewhisperer/QEndpointsTest.kt | 34 +++++++++++++++---- .../services/amazonq/profile/QEndpoints.kt | 24 ++++++++----- .../amazonq/profile/QRegionProfileManager.kt | 2 +- .../src/main/resources/META-INF/plugin.xml | 3 +- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt index 8d74ecca7e0..7f3a40781d8 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererEndpointCustomizer.kt @@ -22,6 +22,7 @@ import software.amazon.awssdk.services.codewhispererruntime.CodeWhispererRuntime import software.amazon.awssdk.services.codewhispererstreaming.CodeWhispererStreamingAsyncClientBuilder import software.aws.toolkits.core.ToolkitClientCustomizer import software.aws.toolkits.core.utils.tryOrNull +import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings import java.net.Proxy @@ -42,7 +43,7 @@ class CodeWhispererEndpointCustomizer : ToolkitClientCustomizer { if (builder is CodeWhispererRuntimeClientBuilder || builder is CodeWhispererStreamingAsyncClientBuilder) { val endpoint = tryOrNull { QEndpoints.getQEndpointWithRegion(regionId) } ?.let { URI.create(it) } - ?: URI.create(QEndpoints.Q_DEFAULT_SERVICE_CONFIG.ENDPOINT) + ?: URI.create(QDefaultServiceConfig.ENDPOINT) builder .endpointOverride(endpoint) .region(Region.of(regionId)) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt index 057c61030f7..09636a4ffe5 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt @@ -4,15 +4,18 @@ package software.aws.toolkits.jetbrains.services.codewhisperer import com.intellij.testFramework.ApplicationExtension -import com.intellij.testFramework.fixtures.BasePlatformTestCase +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.RegisterExtension +import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints +import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionEndpoint import software.aws.toolkits.jetbrains.utils.rules.RegistryExtension +import software.aws.toolkits.jetbrains.utils.satisfiesKt @ExtendWith(ApplicationExtension::class) -class QEndpointsTest : BasePlatformTestCase() { +class QEndpointsTest { @JvmField @RegisterExtension @@ -30,12 +33,29 @@ class QEndpointsTest : BasePlatformTestCase() { registryExtension.setValue("amazon.q.endpoints.json", testJson) val parsed = QEndpoints.listRegionEndpoints() - assertEquals(2, parsed.size) + assertThat(parsed).hasSize(2) + .satisfiesKt { endpoints -> + assertThat(endpoints).satisfiesExactlyInAnyOrder( + { assertThat(it).isEqualTo(QRegionEndpoint("us-east-1", "https://codewhisperer.us-east-1.amazonaws.com/")) }, + { assertThat(it).isEqualTo(QRegionEndpoint("eu-central-1", "https://rts.prod-eu-central-1.codewhisperer.ai.aws.dev/")) }, + ) + } + } + + @Test + fun `uses default entries if blank`() { + registryExtension.setValue("amazon.q.endpoints.json", "") + + assertThat(QEndpoints.listRegionEndpoints()).isEqualTo(QDefaultServiceConfig.ENDPOINT_MAP.toEndpointList()) + } + - val iad = parsed.first { it.region == "us-east-1" } - assertEquals("https://codewhisperer.us-east-1.amazonaws.com/", iad.endpoint) + @Test + fun `uses default entries if invalid`() { + registryExtension.setValue("amazon.q.endpoints.json", "asdfadfkajdklf32.4;'2l4;234l23.424';1l1!!@#!") - val fra = parsed.first { it.region == "eu-central-1" } - assertEquals("https://rts.prod-eu-central-1.codewhisperer.ai.aws.dev/", fra.endpoint) + assertThat(QEndpoints.listRegionEndpoints()).isEqualTo(QDefaultServiceConfig.ENDPOINT_MAP.toEndpointList()) } + + private fun Map.toEndpointList() = map { (region, endpoint) -> QRegionEndpoint(region, endpoint) } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt index 8119def3686..fb732109eb2 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QEndpoints.kt @@ -8,23 +8,29 @@ import com.intellij.openapi.util.registry.Registry import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.warn -object QEndpoints { - private val LOG = getLogger() - data class QRegionEndpoint(val region: String, val endpoint: String) +data class QRegionEndpoint(val region: String, val endpoint: String) - object Q_DEFAULT_SERVICE_CONFIG { - const val REGION = "us-east-1" - const val ENDPOINT = "https://codewhisperer.us-east-1.amazonaws.com/" - } +object QDefaultServiceConfig { + const val REGION = "us-east-1" + const val ENDPOINT = "https://codewhisperer.us-east-1.amazonaws.com/" + + val ENDPOINT_MAP = mapOf( + "us-east-1" to "https://q.us-east-1.amazonaws.com/", + "eu-central-1" to "https://q.eu-central-1.amazonaws.com/" + ) +} + +object QEndpoints { + private val LOG = getLogger() private fun parseEndpoints(): Map { - val rawJson = Registry.get("amazon.q.endpoints.json").asString().takeIf { it.isNotBlank() } ?: return emptyMap() + val rawJson = Registry.get("amazon.q.endpoints.json").asString().takeIf { it.isNotBlank() } ?: return QDefaultServiceConfig.ENDPOINT_MAP return try { val regionList: List = jacksonObjectMapper().readValue(rawJson) regionList.associate { it.region to it.endpoint } } catch (e: Exception) { LOG.warn(e) { "Failed to parse amazon.q.endpoints.json: $rawJson" } - emptyMap() + QDefaultServiceConfig.ENDPOINT_MAP } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt index a835a145a24..c5c437619f6 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt @@ -157,7 +157,7 @@ class QRegionProfileManager : PersistentStateComponent, Disposabl } val settings = conn.getConnectionSettings() - val awsRegion = AwsRegionProvider.getInstance()[QEndpoints.Q_DEFAULT_SERVICE_CONFIG.REGION] ?: error("unknown region from Q default service config") + val awsRegion = AwsRegionProvider.getInstance()[QDefaultServiceConfig.REGION] ?: error("unknown region from Q default service config") // TODO: different window should be able to select different profile return activeProfile(project)?.let { profile -> diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 527df280cb8..5853fc4950d 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -87,8 +87,7 @@ From b3d149a37d167e85b8d16ed45258a855170272ec Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 10 Apr 2025 09:42:20 -0700 Subject: [PATCH 2/2] lint --- .../toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt index 09636a4ffe5..acb685f8551 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/QEndpointsTest.kt @@ -49,7 +49,6 @@ class QEndpointsTest { assertThat(QEndpoints.listRegionEndpoints()).isEqualTo(QDefaultServiceConfig.ENDPOINT_MAP.toEndpointList()) } - @Test fun `uses default entries if invalid`() { registryExtension.setValue("amazon.q.endpoints.json", "asdfadfkajdklf32.4;'2l4;234l23.424';1l1!!@#!")