diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index af930a3a34d..302ee885009 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -427,6 +427,19 @@ Example:
}
```
+Overrides specifically for the Amazon Q language server can be set using the `aws.dev.amazonqLsp` setting. This is a JSON object consisting of keys/values required to override language server: `manifestUrl`, `supportedVersions`, `id`, and `locationOverride`.
+
+Example:
+
+```json
+"aws.dev.amazonqLsp": {
+ "manifestUrl": "https://custom.url/manifest.json",
+ "supportedVersions": "4.0.0",
+ "id": "AmazonQ",
+ "locationOverride": "/custom/path/to/local/lsp/folder",
+}
+```
+
### Environment variables
Environment variables can be used to modify the behaviour of VSCode. The following are environment variables that can be used to configure the extension:
@@ -472,6 +485,10 @@ Unlike the user setting overrides, not all of these environment variables have t
- `__CODEWHISPERER_REGION`: for aws.dev.codewhispererService.region
- `__CODEWHISPERER_ENDPOINT`: for aws.dev.codewhispererService.endpoint
+- `__AMAZONQLSP_MANIFEST_URL`: for aws.dev.amazonqLsp.manifestUrl
+- `__AMAZONQLSP_SUPPORTED_VERSIONS`: for aws.dev.amazonqLsp.supportedVersions
+- `__AMAZONQLSP_ID`: for aws.dev.amazonqLsp.id
+- `__AMAZONQLSP_LOCATION_OVERRIDE`: for aws.dev.amazonqLsp.locationOverride
#### Lambda
diff --git a/packages/amazonq/.vscode/launch.json b/packages/amazonq/.vscode/launch.json
index 9ff3c01849c..d2182273882 100644
--- a/packages/amazonq/.vscode/launch.json
+++ b/packages/amazonq/.vscode/launch.json
@@ -14,7 +14,7 @@
"env": {
"SSMDOCUMENT_LANGUAGESERVER_PORT": "6010",
"WEBPACK_DEVELOPER_SERVER": "http://localhost:8080"
- // "AWS_LANGUAGE_SERVER_OVERRIDE": "${workspaceFolder}/../../../language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js",
+ // "__AMAZONQLSP_LOCATION_OVERRIDE": "${workspaceFolder}/../../../language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js",
},
"envFile": "${workspaceFolder}/.local.env",
"outFiles": ["${workspaceFolder}/dist/**/*.js", "${workspaceFolder}/../core/dist/**/*.js"],
diff --git a/packages/amazonq/src/lsp/config.ts b/packages/amazonq/src/lsp/config.ts
new file mode 100644
index 00000000000..44b08fbc396
--- /dev/null
+++ b/packages/amazonq/src/lsp/config.ts
@@ -0,0 +1,28 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DevSettings, getServiceEnvVarConfig } from 'aws-core-vscode/shared'
+
+export interface AmazonQLspConfig {
+ manifestUrl: string
+ supportedVersions: string
+ id: string
+ locationOverride?: string
+}
+
+export const defaultAmazonQLspConfig: AmazonQLspConfig = {
+ manifestUrl: 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json',
+ supportedVersions: '^3.1.1',
+ id: 'AmazonQ', // used for identification in global storage/local disk location. Do not change.
+ locationOverride: undefined,
+}
+
+export function getAmazonQLspConfig(): AmazonQLspConfig {
+ return {
+ ...defaultAmazonQLspConfig,
+ ...(DevSettings.instance.getServiceConfig('amazonqLsp', {}) as AmazonQLspConfig),
+ ...getServiceEnvVarConfig('amazonqLsp', Object.keys(defaultAmazonQLspConfig)),
+ }
+}
diff --git a/packages/amazonq/src/lsp/lspInstaller.ts b/packages/amazonq/src/lsp/lspInstaller.ts
index b872422f048..e3be27a9580 100644
--- a/packages/amazonq/src/lsp/lspInstaller.ts
+++ b/packages/amazonq/src/lsp/lspInstaller.ts
@@ -16,37 +16,34 @@ import {
} from 'aws-core-vscode/shared'
import path from 'path'
import { Range } from 'semver'
+import { getAmazonQLspConfig } from './config'
-export const manifestURL = 'https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json'
-export const supportedLspServerVersions = new Range('^3.1.1', {
- includePrerelease: true,
-})
const logger = getLogger('amazonqLsp')
export class AmazonQLSPResolver implements LspResolver {
async resolve(): Promise {
- const overrideLocation = process.env.AWS_LANGUAGE_SERVER_OVERRIDE
- if (overrideLocation) {
- logger.info(`Using language server override location: ${overrideLocation}`)
- void vscode.window.showInformationMessage(`Using language server override location: ${overrideLocation}`)
+ const { id, manifestUrl, supportedVersions, locationOverride } = getAmazonQLspConfig()
+ if (locationOverride) {
+ void vscode.window.showInformationMessage(`Using language server override location: ${locationOverride}`)
return {
- assetDirectory: overrideLocation,
+ assetDirectory: locationOverride,
location: 'override',
version: '0.0.0',
resourcePaths: {
- lsp: overrideLocation,
+ lsp: locationOverride,
node: getNodeExecutableName(),
},
}
}
// "AmazonQ" is shared across toolkits to provide a common access point, don't change it
- const name = 'AmazonQ'
- const manifest = await new ManifestResolver(manifestURL, name).resolve()
+ const manifest = await new ManifestResolver(manifestUrl, id).resolve()
const installationResult = await new LanguageServerResolver(
manifest,
- name,
- supportedLspServerVersions
+ id,
+ new Range(supportedVersions, {
+ includePrerelease: true,
+ })
).resolve()
const nodePath = path.join(installationResult.assetDirectory, `servers/${getNodeExecutableName()}`)
diff --git a/packages/amazonq/test/e2e/lsp/lspInstaller.test.ts b/packages/amazonq/test/e2e/lsp/lspInstaller.test.ts
index 04638c02c39..0447d7f5fcf 100644
--- a/packages/amazonq/test/e2e/lsp/lspInstaller.test.ts
+++ b/packages/amazonq/test/e2e/lsp/lspInstaller.test.ts
@@ -5,8 +5,9 @@
import assert from 'assert'
import sinon from 'sinon'
-import { AmazonQLSPResolver, manifestURL, supportedLspServerVersions } from '../../../src/lsp/lspInstaller'
+import { AmazonQLSPResolver } from '../../../src/lsp/lspInstaller'
import {
+ DevSettings,
fs,
globals,
LanguageServerResolver,
@@ -19,6 +20,7 @@ import * as semver from 'semver'
import { assertTelemetry } from 'aws-core-vscode/test'
import { LspController } from 'aws-core-vscode/amazonq'
import { LanguageServerSetup } from 'aws-core-vscode/telemetry'
+import { AmazonQLspConfig, getAmazonQLspConfig } from '../../../src/lsp/config'
function createVersion(version: string) {
return {
@@ -48,9 +50,11 @@ describe('AmazonQLSPInstaller', () => {
// If globalState contains an ETag that is up to date with remote, we won't fetch it resulting in inconsistent behavior.
// Therefore, we clear it temporarily for these tests to ensure consistent behavior.
let manifestStorage: { [key: string]: any }
+ let lspConfig: AmazonQLspConfig
before(async () => {
manifestStorage = globals.globalState.get(manifestStorageKey) || {}
+ lspConfig = getAmazonQLspConfig()
})
beforeEach(async () => {
@@ -64,7 +68,7 @@ describe('AmazonQLSPInstaller', () => {
})
afterEach(async () => {
- delete process.env.AWS_LANGUAGE_SERVER_OVERRIDE
+ delete process.env.__AMAZONQLSP_LOCATION_OVERRIDE
sandbox.restore()
await fs.delete(tempDir, {
recursive: true,
@@ -76,9 +80,25 @@ describe('AmazonQLSPInstaller', () => {
})
describe('resolve()', () => {
- it('uses AWS_LANGUAGE_SERVER_OVERRIDE', async () => {
+ it('uses dev setting override', async () => {
+ const locationOverride = '/custom/path/to/lsp'
+ const serviceConfigStub = sandbox.stub().returns({
+ locationOverride,
+ })
+ sandbox.stub(DevSettings, 'instance').get(() => ({
+ getServiceConfig: serviceConfigStub,
+ }))
+
+ const result = await resolver.resolve()
+
+ assert.strictEqual(result.assetDirectory, locationOverride)
+ assert.strictEqual(result.location, 'override')
+ assert.strictEqual(result.version, '0.0.0')
+ })
+
+ it('uses environment variable override', async () => {
const overridePath = '/custom/path/to/lsp'
- process.env.AWS_LANGUAGE_SERVER_OVERRIDE = overridePath
+ process.env.__AMAZONQLSP_LOCATION_OVERRIDE = overridePath
const result = await resolver.resolve()
@@ -93,14 +113,14 @@ describe('AmazonQLSPInstaller', () => {
assert.ok(download.assetDirectory.startsWith(tempDir))
assert.deepStrictEqual(download.location, 'remote')
- assert.ok(semver.satisfies(download.version, supportedLspServerVersions))
+ assert.ok(semver.satisfies(download.version, lspConfig.supportedVersions))
// Second try - Should see the contents in the cache
const cache = await resolver.resolve()
assert.ok(cache.assetDirectory.startsWith(tempDir))
assert.deepStrictEqual(cache.location, 'cache')
- assert.ok(semver.satisfies(cache.version, supportedLspServerVersions))
+ assert.ok(semver.satisfies(cache.version, lspConfig.supportedVersions))
/**
* Always make sure the latest version is one patch higher. This stops a problem
@@ -135,7 +155,7 @@ describe('AmazonQLSPInstaller', () => {
assert.ok(fallback.assetDirectory.startsWith(tempDir))
assert.deepStrictEqual(fallback.location, 'fallback')
- assert.ok(semver.satisfies(fallback.version, supportedLspServerVersions))
+ assert.ok(semver.satisfies(fallback.version, lspConfig.supportedVersions))
/* First Try Telemetry
getManifest: remote succeeds
@@ -144,25 +164,25 @@ describe('AmazonQLSPInstaller', () => {
*/
const firstTryTelemetry: Partial[] = [
{
- id: 'AmazonQ',
+ id: lspConfig.id,
manifestLocation: 'remote',
languageServerSetupStage: 'getManifest',
result: 'Succeeded',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'cache',
languageServerSetupStage: 'getServer',
result: 'Failed',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'remote',
languageServerSetupStage: 'validate',
result: 'Succeeded',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'remote',
languageServerSetupStage: 'getServer',
result: 'Succeeded',
@@ -176,19 +196,19 @@ describe('AmazonQLSPInstaller', () => {
*/
const secondTryTelemetry: Partial[] = [
{
- id: 'AmazonQ',
+ id: lspConfig.id,
manifestLocation: 'remote',
languageServerSetupStage: 'getManifest',
result: 'Failed',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
manifestLocation: 'cache',
languageServerSetupStage: 'getManifest',
result: 'Succeeded',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'cache',
languageServerSetupStage: 'getServer',
result: 'Succeeded',
@@ -202,19 +222,19 @@ describe('AmazonQLSPInstaller', () => {
*/
const thirdTryTelemetry: Partial[] = [
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'cache',
languageServerSetupStage: 'getServer',
result: 'Failed',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'remote',
languageServerSetupStage: 'getServer',
result: 'Failed',
},
{
- id: 'AmazonQ',
+ id: lspConfig.id,
languageServerLocation: 'fallback',
languageServerSetupStage: 'getServer',
result: 'Succeeded',
@@ -227,7 +247,7 @@ describe('AmazonQLSPInstaller', () => {
})
it('resolves release candidiates', async () => {
- const original = new ManifestResolver(manifestURL, 'AmazonQ').resolve()
+ const original = new ManifestResolver(lspConfig.manifestUrl, lspConfig.id).resolve()
sandbox.stub(ManifestResolver.prototype, 'resolve').callsFake(async () => {
const originalManifest = await original
diff --git a/packages/amazonq/test/unit/amazonq/lsp/config.test.ts b/packages/amazonq/test/unit/amazonq/lsp/config.test.ts
new file mode 100644
index 00000000000..c26e4a87239
--- /dev/null
+++ b/packages/amazonq/test/unit/amazonq/lsp/config.test.ts
@@ -0,0 +1,79 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import assert from 'assert'
+import { DevSettings } from 'aws-core-vscode/shared'
+import sinon from 'sinon'
+import { defaultAmazonQLspConfig, getAmazonQLspConfig } from '../../../../src/lsp/config'
+
+describe('getAmazonQLspConfig', () => {
+ let sandbox: sinon.SinonSandbox
+ let serviceConfigStub: sinon.SinonStub
+ const settingConfig = {
+ manifestUrl: 'https://custom.url/manifest.json',
+ supportedVersions: '4.0.0',
+ id: 'AmazonQSetting',
+ locationOverride: '/custom/path',
+ }
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox()
+
+ serviceConfigStub = sandbox.stub()
+ sandbox.stub(DevSettings, 'instance').get(() => ({
+ getServiceConfig: serviceConfigStub,
+ }))
+ })
+
+ afterEach(() => {
+ sandbox.restore()
+ })
+
+ it('uses default config', () => {
+ serviceConfigStub.returns({})
+ const config = getAmazonQLspConfig()
+ assert.deepStrictEqual(config, defaultAmazonQLspConfig)
+ })
+
+ it('overrides location', () => {
+ const locationOverride = '/custom/path/to/lsp'
+ serviceConfigStub.returns({ locationOverride })
+
+ const config = getAmazonQLspConfig()
+ assert.deepStrictEqual(config, {
+ ...defaultAmazonQLspConfig,
+ locationOverride,
+ })
+ })
+
+ it('overrides default settings', () => {
+ serviceConfigStub.returns(settingConfig)
+
+ const config = getAmazonQLspConfig()
+ assert.deepStrictEqual(config, settingConfig)
+ })
+
+ it('environment variable takes precedence over settings', () => {
+ const envConfig = {
+ manifestUrl: 'https://another-custom.url/manifest.json',
+ supportedVersions: '5.1.1',
+ id: 'AmazonQEnv',
+ locationOverride: '/some/new/custom/path',
+ }
+
+ process.env.__AMAZONQLSP_MANIFEST_URL = envConfig.manifestUrl
+ process.env.__AMAZONQLSP_SUPPORTED_VERSIONS = envConfig.supportedVersions
+ process.env.__AMAZONQLSP_ID = envConfig.id
+ process.env.__AMAZONQLSP_LOCATION_OVERRIDE = envConfig.locationOverride
+
+ serviceConfigStub.returns(settingConfig)
+
+ const config = getAmazonQLspConfig()
+ assert.deepStrictEqual(config, {
+ ...defaultAmazonQLspConfig,
+ ...envConfig,
+ })
+ })
+})
diff --git a/packages/core/src/shared/index.ts b/packages/core/src/shared/index.ts
index b45948308bf..6193a68445c 100644
--- a/packages/core/src/shared/index.ts
+++ b/packages/core/src/shared/index.ts
@@ -18,7 +18,7 @@ export * from './extensionUtilities'
export * from './extensionStartup'
export { RegionProvider } from './regions/regionProvider'
export { Commands } from './vscode/commands2'
-export { getMachineId } from './vscode/env'
+export { getMachineId, getServiceEnvVarConfig } from './vscode/env'
export { getLogger } from './logger/logger'
export { activateExtension, openUrl } from './utilities/vsCodeUtils'
export { waitUntil, sleep, Timeout } from './utilities/timeoutUtils'
diff --git a/packages/core/src/shared/settings.ts b/packages/core/src/shared/settings.ts
index a486784fe14..09b7396f0d1 100644
--- a/packages/core/src/shared/settings.ts
+++ b/packages/core/src/shared/settings.ts
@@ -757,6 +757,7 @@ const devSettings = {
endpoints: Record(String, String),
codecatalystService: Record(String, String),
codewhispererService: Record(String, String),
+ amazonqLsp: Record(String, String),
ssoCacheDirectory: String,
autofillStartUrl: String,
webAuth: Boolean,
@@ -769,6 +770,7 @@ type ServiceClients = keyof ServiceTypeMap
interface ServiceTypeMap {
codecatalystService: codecatalyst.CodeCatalystConfig
codewhispererService: codewhisperer.CodeWhispererConfig
+ amazonqLsp: object // type is provided inside of amazon q
}
/**