diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 5472f86d88..61dc88f5e1 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,9 +1,9 @@
{
- "chat-client": "0.1.29",
- "core/aws-lsp-core": "0.0.12",
- "server/aws-lsp-antlr4": "0.1.16",
- "server/aws-lsp-codewhisperer": "0.0.70",
- "server/aws-lsp-json": "0.1.16",
- "server/aws-lsp-partiql": "0.0.15",
- "server/aws-lsp-yaml": "0.1.16"
+ "chat-client": "0.1.30",
+ "core/aws-lsp-core": "0.0.13",
+ "server/aws-lsp-antlr4": "0.1.17",
+ "server/aws-lsp-codewhisperer": "0.0.71",
+ "server/aws-lsp-json": "0.1.17",
+ "server/aws-lsp-partiql": "0.0.16",
+ "server/aws-lsp-yaml": "0.1.17"
}
diff --git a/README.md b/README.md
index fad70742fe..0578e63646 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# Language Servers for AWS
[](https://codecov.io/github/aws/language-servers)
+[](https://github.com/aws/language-servers/actions/workflows/integration-tests.yml)
Language servers for integration with IDEs and Editors, which implement the protocol (LSP extensions) defined in the [language-server-runtimes](https://github.com/aws/language-server-runtimes/tree/main/runtimes) repo.
diff --git a/app/aws-lsp-antlr4-runtimes/package.json b/app/aws-lsp-antlr4-runtimes/package.json
index ee7ca8507f..e283033afb 100644
--- a/app/aws-lsp-antlr4-runtimes/package.json
+++ b/app/aws-lsp-antlr4-runtimes/package.json
@@ -12,7 +12,7 @@
"webpack": "webpack"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-antlr4": "*",
"antlr4-c3": "^3.4.1",
"antlr4ng": "^3.0.4"
diff --git a/app/aws-lsp-buildspec-runtimes/package.json b/app/aws-lsp-buildspec-runtimes/package.json
index 217c3c3780..0fa2217def 100644
--- a/app/aws-lsp-buildspec-runtimes/package.json
+++ b/app/aws-lsp-buildspec-runtimes/package.json
@@ -7,7 +7,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-buildspec": "^0.0.1"
}
}
diff --git a/app/aws-lsp-cloudformation-runtimes/package.json b/app/aws-lsp-cloudformation-runtimes/package.json
index f77789daa8..336e9b48ae 100644
--- a/app/aws-lsp-cloudformation-runtimes/package.json
+++ b/app/aws-lsp-cloudformation-runtimes/package.json
@@ -7,7 +7,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-cloudformation": "^0.0.1"
}
}
diff --git a/app/aws-lsp-codewhisperer-runtimes/package.json b/app/aws-lsp-codewhisperer-runtimes/package.json
index c6d37ce276..6a1721382c 100644
--- a/app/aws-lsp-codewhisperer-runtimes/package.json
+++ b/app/aws-lsp-codewhisperer-runtimes/package.json
@@ -22,7 +22,7 @@
"local-build": "node scripts/local-build.js"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-codewhisperer": "*",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
diff --git a/app/aws-lsp-identity-runtimes/package.json b/app/aws-lsp-identity-runtimes/package.json
index 61ed544ed9..b4971eae87 100644
--- a/app/aws-lsp-identity-runtimes/package.json
+++ b/app/aws-lsp-identity-runtimes/package.json
@@ -7,7 +7,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-identity": "^0.0.1"
}
}
diff --git a/app/aws-lsp-json-runtimes/package.json b/app/aws-lsp-json-runtimes/package.json
index 4fafe0aa34..9fa7b7e958 100644
--- a/app/aws-lsp-json-runtimes/package.json
+++ b/app/aws-lsp-json-runtimes/package.json
@@ -11,7 +11,7 @@
"webpack": "webpack"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-json": "*"
},
"devDependencies": {
diff --git a/app/aws-lsp-notification-runtimes/package.json b/app/aws-lsp-notification-runtimes/package.json
index d07d7bfe5e..a355a967bf 100644
--- a/app/aws-lsp-notification-runtimes/package.json
+++ b/app/aws-lsp-notification-runtimes/package.json
@@ -7,7 +7,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-notification": "^0.0.1"
}
}
diff --git a/app/aws-lsp-s3-runtimes/package.json b/app/aws-lsp-s3-runtimes/package.json
index 75ced42770..1c15a9af5e 100644
--- a/app/aws-lsp-s3-runtimes/package.json
+++ b/app/aws-lsp-s3-runtimes/package.json
@@ -10,7 +10,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-s3": "^0.0.1"
}
}
diff --git a/app/aws-lsp-yaml-json-webworker/package.json b/app/aws-lsp-yaml-json-webworker/package.json
index 57fb02a614..bfb5fa6277 100644
--- a/app/aws-lsp-yaml-json-webworker/package.json
+++ b/app/aws-lsp-yaml-json-webworker/package.json
@@ -11,7 +11,7 @@
"serve:webpack": "NODE_ENV=development webpack serve"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-json": "*",
"@aws/lsp-yaml": "*"
},
diff --git a/app/aws-lsp-yaml-runtimes/package.json b/app/aws-lsp-yaml-runtimes/package.json
index 26c8362e4b..86638b8ffc 100644
--- a/app/aws-lsp-yaml-runtimes/package.json
+++ b/app/aws-lsp-yaml-runtimes/package.json
@@ -11,7 +11,7 @@
"webpack": "webpack"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-yaml": "*"
},
"devDependencies": {
diff --git a/app/hello-world-lsp-runtimes/package.json b/app/hello-world-lsp-runtimes/package.json
index d2cfb65981..2ccccc694b 100644
--- a/app/hello-world-lsp-runtimes/package.json
+++ b/app/hello-world-lsp-runtimes/package.json
@@ -15,7 +15,7 @@
},
"dependencies": {
"@aws/hello-world-lsp": "^0.0.1",
- "@aws/language-server-runtimes": "^0.2.120"
+ "@aws/language-server-runtimes": "^0.2.121"
},
"devDependencies": {
"@types/chai": "^4.3.5",
diff --git a/chat-client/CHANGELOG.md b/chat-client/CHANGELOG.md
index 97042b92be..41178f1cb7 100644
--- a/chat-client/CHANGELOG.md
+++ b/chat-client/CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog
+## [0.1.30](https://github.com/aws/language-servers/compare/chat-client/v0.1.29...chat-client/v0.1.30) (2025-08-04)
+
+
+### Features
+
+* support http transport without authorization for MCP ([97e806c](https://github.com/aws/language-servers/commit/97e806ce7ea5e5be1fd60c4a4d9a54cf76c8f8cb))
+
+
+### Bug Fixes
+
+* **amazonq:** fix the issue that invalid image notification always show ([#2007](https://github.com/aws/language-servers/issues/2007)) ([ceed762](https://github.com/aws/language-servers/commit/ceed762ace5f94cb0e0a7978eb6c4894bc11ce42))
+* **amazonq:** improve cross theme support ([#2036](https://github.com/aws/language-servers/issues/2036)) ([002a255](https://github.com/aws/language-servers/commit/002a255c28ea07ca6623dbd074101cbc6082ceb8))
+* **amazonq:** improve welcome screen and enable tips ([#2035](https://github.com/aws/language-servers/issues/2035)) ([ac00b94](https://github.com/aws/language-servers/commit/ac00b94df45c2bba0666423c937757fad4db95cc))
+* **amazonq:** refactor the welcome screen to make it look better ([#2027](https://github.com/aws/language-servers/issues/2027)) ([1f7c608](https://github.com/aws/language-servers/commit/1f7c608ba2f89c8b0675e62451e27d2dc547613c))
+* enable test flag for amazon q ui testing ([#2046](https://github.com/aws/language-servers/issues/2046)) ([f18ea96](https://github.com/aws/language-servers/commit/f18ea96c1e5cd9b93974a047d7f2bb1aba0d9436))
+* use new language server runtime ([#2023](https://github.com/aws/language-servers/issues/2023)) ([83ea1e4](https://github.com/aws/language-servers/commit/83ea1e42fe52990696eb9b878fa11e2c5331bec5))
+
## [0.1.29](https://github.com/aws/language-servers/compare/chat-client/v0.1.28...chat-client/v0.1.29) (2025-07-29)
diff --git a/chat-client/package.json b/chat-client/package.json
index ed62732005..9eb33a6a11 100644
--- a/chat-client/package.json
+++ b/chat-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@aws/chat-client",
- "version": "0.1.29",
+ "version": "0.1.30",
"description": "AWS Chat Client",
"main": "out/index.js",
"repository": {
@@ -25,7 +25,7 @@
},
"dependencies": {
"@aws/chat-client-ui-types": "^0.1.56",
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/language-server-runtimes-types": "^0.1.50",
"@aws/mynah-ui": "^4.36.2"
},
diff --git a/chat-client/src/client/mynahUi.ts b/chat-client/src/client/mynahUi.ts
index 3de8842fae..3b42249331 100644
--- a/chat-client/src/client/mynahUi.ts
+++ b/chat-client/src/client/mynahUi.ts
@@ -810,6 +810,7 @@ export const createMynahUi = (
},
config: {
maxTabs: 10,
+ test: true,
dragOverlayIcon: MynahIcons.IMAGE,
texts: {
...uiComponentsTexts,
diff --git a/chat-client/src/client/tabs/tabFactory.ts b/chat-client/src/client/tabs/tabFactory.ts
index d5371b0bcd..af1af21e7d 100644
--- a/chat-client/src/client/tabs/tabFactory.ts
+++ b/chat-client/src/client/tabs/tabFactory.ts
@@ -77,7 +77,7 @@ export class TabFactory {
body: `
Amazon Q
-
+
Did you know?
${this.getRandomTip()}
diff --git a/client/vscode/package.json b/client/vscode/package.json
index 30abd111fd..4b8c2fdb4c 100644
--- a/client/vscode/package.json
+++ b/client/vscode/package.json
@@ -352,7 +352,7 @@
"@aws-sdk/credential-providers": "^3.731.1",
"@aws-sdk/types": "^3.734.0",
"@aws/chat-client-ui-types": "^0.1.56",
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@types/uuid": "^9.0.8",
"@types/vscode": "^1.98.0",
"jose": "^5.2.4",
diff --git a/core/aws-lsp-core/CHANGELOG.md b/core/aws-lsp-core/CHANGELOG.md
index 6861a1f46e..9e2e616f55 100644
--- a/core/aws-lsp-core/CHANGELOG.md
+++ b/core/aws-lsp-core/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## [0.0.13](https://github.com/aws/language-servers/compare/lsp-core/v0.0.12...lsp-core/v0.0.13) (2025-08-04)
+
+
+### Bug Fixes
+
+* use new language server runtime ([#2023](https://github.com/aws/language-servers/issues/2023)) ([83ea1e4](https://github.com/aws/language-servers/commit/83ea1e42fe52990696eb9b878fa11e2c5331bec5))
+
## [0.0.12](https://github.com/aws/language-servers/compare/lsp-core/v0.0.11...lsp-core/v0.0.12) (2025-07-17)
diff --git a/core/aws-lsp-core/package.json b/core/aws-lsp-core/package.json
index 453b1aac0c..4c033448eb 100644
--- a/core/aws-lsp-core/package.json
+++ b/core/aws-lsp-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@aws/lsp-core",
- "version": "0.0.12",
+ "version": "0.0.13",
"description": "Core library, contains common code and utilities",
"main": "out/index.js",
"repository": {
@@ -28,7 +28,7 @@
"prepack": "shx cp ../../LICENSE ../../NOTICE ../../SECURITY.md ."
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@gerhobbelt/gitignore-parser": "^0.2.0-9",
"cross-spawn": "7.0.6",
"jose": "^5.2.4",
diff --git a/integration-tests/q-agentic-chat-server/package.json b/integration-tests/q-agentic-chat-server/package.json
index af4c76ba96..26202df0c1 100644
--- a/integration-tests/q-agentic-chat-server/package.json
+++ b/integration-tests/q-agentic-chat-server/package.json
@@ -4,12 +4,12 @@
"description": "Integration tests for Q Agentic Chat Server",
"main": "out/index.js",
"scripts": {
- "clean": "rm -rf out/ node_modules/ tsconfig.tsbuildinfo .tsbuildinfo",
+ "clean": "rimraf out/ node_modules/ tsconfig.tsbuildinfo .tsbuildinfo",
"compile": "tsc --build && cp -r src/tests/testFixture out/tests/",
"test-integ": "npm run compile && mocha --timeout 30000 \"./out/**/*.test.js\" --retries 2"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "*"
},
"devDependencies": {
@@ -22,6 +22,7 @@
"jose": "^5.10.0",
"json-rpc-2.0": "^1.7.1",
"mocha": "^11.0.1",
+ "rimraf": "^3.0.2",
"typescript": "^5.0.0",
"yauzl-promise": "^4.0.0"
}
diff --git a/integration-tests/q-agentic-chat-server/src/tests/agenticChatInteg.test.ts b/integration-tests/q-agentic-chat-server/src/tests/agenticChatInteg.test.ts
index 6e0d0dcede..897ee02071 100644
--- a/integration-tests/q-agentic-chat-server/src/tests/agenticChatInteg.test.ts
+++ b/integration-tests/q-agentic-chat-server/src/tests/agenticChatInteg.test.ts
@@ -8,7 +8,7 @@ import { JSONRPCEndpoint, LspClient } from './lspClient'
import { pathToFileURL } from 'url'
import * as crypto from 'crypto'
import { EncryptionInitialization } from '@aws/lsp-core'
-import { authenticateServer, decryptObjectWithKey, encryptObjectWithKey } from './testUtils'
+import { authenticateServer, decryptObjectWithKey, encryptObjectWithKey, normalizePath } from './testUtils'
import { ChatParams, ChatResult } from '@aws/language-server-runtimes/protocol'
import * as fs from 'fs'
@@ -172,7 +172,9 @@ describe('Q Agentic Chat Server Integration Tests', async () => {
msg => msg.type === 'tool' && msg.fileList?.rootFolderTitle === '1 file read'
)
expect(fsReadMessage).to.exist
- expect(fsReadMessage?.fileList?.filePaths).to.include.members([path.join(rootPath, 'test.py')])
+ const expectedPath = path.join(rootPath, 'test.py')
+ const actualPaths = fsReadMessage?.fileList?.filePaths?.map(normalizePath) || []
+ expect(actualPaths).to.include.members([normalizePath(expectedPath)])
expect(fsReadMessage?.messageId?.startsWith('tooluse_')).to.be.true
})
@@ -192,7 +194,8 @@ describe('Q Agentic Chat Server Integration Tests', async () => {
msg => msg.type === 'tool' && msg.fileList?.rootFolderTitle === '1 directory listed'
)
expect(listDirectoryMessage).to.exist
- expect(listDirectoryMessage?.fileList?.filePaths).to.include.members([rootPath])
+ const actualPaths = listDirectoryMessage?.fileList?.filePaths?.map(normalizePath) || []
+ expect(actualPaths).to.include.members([normalizePath(rootPath)])
expect(listDirectoryMessage?.messageId?.startsWith('tooluse_')).to.be.true
})
@@ -216,12 +219,17 @@ describe('Q Agentic Chat Server Integration Tests', async () => {
expect(executeBashMessage?.body).to.include('test.ts')
})
- it('waits for user acceptance when executing mutable bash commands', async () => {
+ it('waits for user acceptance when executing mutable bash commands', async function () {
+ const command =
+ process.platform === 'win32'
+ ? 'echo %date% > timestamp.txt && echo "Timestamp saved"'
+ : 'date > timestamp.txt && echo "Timestamp saved"'
+
const encryptedMessage = await encryptObjectWithKey
(
{
tabId,
prompt: {
- prompt: `Run this command using the executeBash tool: \`date > timestamp.txt && echo "Timestamp saved"\``,
+ prompt: `Run this command using the executeBash tool: \`${command}\``,
},
},
encryptionKey
@@ -367,6 +375,7 @@ describe('Q Agentic Chat Server Integration Tests', async () => {
)
expect(fileSearchMessage).to.exist
expect(fileSearchMessage?.messageId?.startsWith('tooluse_')).to.be.true
- expect(fileSearchMessage?.fileList?.filePaths).to.include.members([rootPath])
+ const actualPaths = fileSearchMessage?.fileList?.filePaths?.map(normalizePath) || []
+ expect(actualPaths).to.include.members([normalizePath(rootPath)])
})
})
diff --git a/integration-tests/q-agentic-chat-server/src/tests/testUtils.ts b/integration-tests/q-agentic-chat-server/src/tests/testUtils.ts
index ae7e5b3905..cd2af069d2 100644
--- a/integration-tests/q-agentic-chat-server/src/tests/testUtils.ts
+++ b/integration-tests/q-agentic-chat-server/src/tests/testUtils.ts
@@ -1,5 +1,6 @@
import { UpdateCredentialsParams } from '@aws/language-server-runtimes/protocol'
import * as jose from 'jose'
+import * as path from 'path'
import { JSONRPCEndpoint } from './lspClient'
/**
@@ -72,3 +73,12 @@ async function setProfile(endpoint: JSONRPCEndpoint, profileArn: string): Promis
settings: { profileArn },
})
}
+
+/**
+ * Normalize paths for cross-platform comparison
+ * @param filePath - The file path to normalize
+ * @returns Normalized path with consistent casing
+ */
+export function normalizePath(filePath: string): string {
+ return path.resolve(filePath).toLowerCase()
+}
diff --git a/package-lock.json b/package-lock.json
index 148726033a..1d8f367c34 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,7 +48,7 @@
"name": "@aws/lsp-antlr4-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-antlr4": "*",
"antlr4-c3": "^3.4.1",
"antlr4ng": "^3.0.4"
@@ -71,7 +71,7 @@
"name": "@aws/lsp-buildspec-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-buildspec": "^0.0.1"
}
},
@@ -79,7 +79,7 @@
"name": "@aws/lsp-cloudformation-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-cloudformation": "^0.0.1"
}
},
@@ -87,7 +87,7 @@
"name": "@aws/lsp-codewhisperer-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-codewhisperer": "*",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
@@ -120,7 +120,7 @@
"name": "@aws/lsp-identity-runtimes",
"version": "0.1.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-identity": "^0.0.1"
}
},
@@ -128,7 +128,7 @@
"name": "@aws/lsp-json-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-json": "*"
},
"devDependencies": {
@@ -148,7 +148,7 @@
"name": "@aws/lsp-notification-runtimes",
"version": "0.1.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-notification": "^0.0.1"
}
},
@@ -181,7 +181,7 @@
"name": "@aws/lsp-s3-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-s3": "^0.0.1"
},
"bin": {
@@ -192,7 +192,7 @@
"name": "@aws/lsp-yaml-json-webworker",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-json": "*",
"@aws/lsp-yaml": "*"
},
@@ -212,7 +212,7 @@
"name": "@aws/lsp-yaml-runtimes",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-yaml": "*"
},
"devDependencies": {
@@ -234,7 +234,7 @@
"version": "0.0.1",
"dependencies": {
"@aws/hello-world-lsp": "^0.0.1",
- "@aws/language-server-runtimes": "^0.2.120"
+ "@aws/language-server-runtimes": "^0.2.121"
},
"devDependencies": {
"@types/chai": "^4.3.5",
@@ -251,11 +251,11 @@
},
"chat-client": {
"name": "@aws/chat-client",
- "version": "0.1.29",
+ "version": "0.1.30",
"license": "Apache-2.0",
"dependencies": {
"@aws/chat-client-ui-types": "^0.1.56",
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/language-server-runtimes-types": "^0.1.50",
"@aws/mynah-ui": "^4.36.2"
},
@@ -280,7 +280,7 @@
"@aws-sdk/credential-providers": "^3.731.1",
"@aws-sdk/types": "^3.734.0",
"@aws/chat-client-ui-types": "^0.1.56",
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@types/uuid": "^9.0.8",
"@types/vscode": "^1.98.0",
"jose": "^5.2.4",
@@ -293,10 +293,10 @@
},
"core/aws-lsp-core": {
"name": "@aws/lsp-core",
- "version": "0.0.12",
+ "version": "0.0.13",
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@gerhobbelt/gitignore-parser": "^0.2.0-9",
"cross-spawn": "7.0.6",
"jose": "^5.2.4",
@@ -327,7 +327,7 @@
"name": "@aws/q-agentic-chat-server-integration-tests",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "*"
},
"devDependencies": {
@@ -340,6 +340,7 @@
"jose": "^5.10.0",
"json-rpc-2.0": "^1.7.1",
"mocha": "^11.0.1",
+ "rimraf": "^3.0.2",
"typescript": "^5.0.0",
"yauzl-promise": "^4.0.0"
}
@@ -4035,12 +4036,12 @@
"link": true
},
"node_modules/@aws/language-server-runtimes": {
- "version": "0.2.120",
- "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes/-/language-server-runtimes-0.2.120.tgz",
- "integrity": "sha512-4zu8sEEVr6OhlrgvPX9vl5HoEhfzipMNQJBfLh/V74XURztnphgrTQHplLh9sHmCLvtv9bnK46xVoOxYrfe9Yg==",
+ "version": "0.2.121",
+ "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes/-/language-server-runtimes-0.2.121.tgz",
+ "integrity": "sha512-DDh3ICNVoEi4nhp4JdkkPTsdlHsF0yt6VgxlMGwP90N1hjA4xVESSSla9pB0TcuMPKlmXqOPQGQvkph9FKerVw==",
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes-types": "^0.1.52",
+ "@aws/language-server-runtimes-types": "^0.1.53",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.200.0",
"@opentelemetry/core": "^2.0.0",
@@ -4067,9 +4068,9 @@
}
},
"node_modules/@aws/language-server-runtimes-types": {
- "version": "0.1.52",
- "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.52.tgz",
- "integrity": "sha512-9z2OiEcWc3CZ6u/j/ABeoGhkwmNbiRqqCO5GJDvtcJfEi9UsSMmo4+YcJJj93pYW8CEVJy3DOPwQCrtT7ngwcw==",
+ "version": "0.1.53",
+ "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.53.tgz",
+ "integrity": "sha512-6KCe/YsqF0SciXm8qg/qVuDXGwQqJgRaqrT6YhZUjqs3mclG9G6Gdwu9YEi8t/NYobNWKw0E+aCXW2TFxJgr7A==",
"license": "Apache-2.0",
"dependencies": {
"vscode-languageserver-textdocument": "^1.0.12",
@@ -28629,11 +28630,11 @@
},
"server/aws-lsp-antlr4": {
"name": "@aws/lsp-antlr4",
- "version": "0.1.16",
+ "version": "0.1.17",
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
- "@aws/lsp-core": "^0.0.12"
+ "@aws/language-server-runtimes": "^0.2.121",
+ "@aws/lsp-core": "^0.0.13"
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.24.1",
@@ -28674,7 +28675,7 @@
"name": "@aws/lsp-buildspec",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-json": "*",
"@aws/lsp-yaml": "*",
"vscode-languageserver": "^9.0.1",
@@ -28685,7 +28686,7 @@
"name": "@aws/lsp-cloudformation",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "*",
"@aws/lsp-json": "*",
"vscode-languageserver": "^9.0.1",
@@ -28694,7 +28695,7 @@
},
"server/aws-lsp-codewhisperer": {
"name": "@aws/lsp-codewhisperer",
- "version": "0.0.70",
+ "version": "0.0.71",
"bundleDependencies": [
"@amzn/codewhisperer-streaming",
"@amzn/amazon-q-developer-streaming-client"
@@ -28707,8 +28708,8 @@
"@aws-sdk/util-arn-parser": "^3.723.0",
"@aws-sdk/util-retry": "^3.374.0",
"@aws/chat-client-ui-types": "^0.1.56",
- "@aws/language-server-runtimes": "^0.2.120",
- "@aws/lsp-core": "^0.0.12",
+ "@aws/language-server-runtimes": "^0.2.121",
+ "@aws/lsp-core": "^0.0.13",
"@modelcontextprotocol/sdk": "^1.15.0",
"@smithy/node-http-handler": "^2.5.0",
"adm-zip": "^0.5.10",
@@ -28847,7 +28848,7 @@
"dependencies": {
"@aws-sdk/client-sso-oidc": "^3.616.0",
"@aws-sdk/token-providers": "^3.744.0",
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "^0.0.12",
"@smithy/node-http-handler": "^3.2.5",
"@smithy/shared-ini-file-loader": "^4.0.1",
@@ -28876,6 +28877,25 @@
"node": ">=18.0.0"
}
},
+ "server/aws-lsp-identity/node_modules/@aws/lsp-core": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@aws/lsp-core/-/lsp-core-0.0.12.tgz",
+ "integrity": "sha512-FNbbvzUZQKtUE/LHGsI8b05+M44h47f7ExlET1WeOnhfjDWu7n+wlHRkPZkdsEe1z5pbluGNBaiTO/cV9A2qQQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws/language-server-runtimes": "^0.2.109",
+ "@gerhobbelt/gitignore-parser": "^0.2.0-9",
+ "cross-spawn": "7.0.6",
+ "jose": "^5.2.4",
+ "request-light": "^0.8.0",
+ "vscode-languageserver-textdocument": "^1.0.8",
+ "vscode-languageserver-types": "^3.17.3",
+ "vscode-uri": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"server/aws-lsp-identity/node_modules/@smithy/types": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz",
@@ -28890,11 +28910,11 @@
},
"server/aws-lsp-json": {
"name": "@aws/lsp-json",
- "version": "0.1.16",
+ "version": "0.1.17",
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
- "@aws/lsp-core": "^0.0.12",
+ "@aws/language-server-runtimes": "^0.2.121",
+ "@aws/lsp-core": "^0.0.13",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.8"
},
@@ -28910,7 +28930,7 @@
"version": "0.0.1",
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "^0.0.12",
"vscode-languageserver": "^9.0.1"
},
@@ -28935,6 +28955,25 @@
"node": ">=18.0.0"
}
},
+ "server/aws-lsp-notification/node_modules/@aws/lsp-core": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@aws/lsp-core/-/lsp-core-0.0.12.tgz",
+ "integrity": "sha512-FNbbvzUZQKtUE/LHGsI8b05+M44h47f7ExlET1WeOnhfjDWu7n+wlHRkPZkdsEe1z5pbluGNBaiTO/cV9A2qQQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws/language-server-runtimes": "^0.2.109",
+ "@gerhobbelt/gitignore-parser": "^0.2.0-9",
+ "cross-spawn": "7.0.6",
+ "jose": "^5.2.4",
+ "request-light": "^0.8.0",
+ "vscode-languageserver-textdocument": "^1.0.8",
+ "vscode-languageserver-types": "^3.17.3",
+ "vscode-uri": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"server/aws-lsp-notification/node_modules/@smithy/types": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.7.2.tgz",
@@ -28949,10 +28988,10 @@
},
"server/aws-lsp-partiql": {
"name": "@aws/lsp-partiql",
- "version": "0.0.15",
+ "version": "0.0.16",
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"antlr4-c3": "3.4.2",
"antlr4ng": "3.0.14",
"web-tree-sitter": "0.22.6"
@@ -28974,20 +29013,39 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.623.0",
"@aws-sdk/types": "^3.734.0",
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "^0.0.12",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.8"
}
},
+ "server/aws-lsp-s3/node_modules/@aws/lsp-core": {
+ "version": "0.0.12",
+ "resolved": "https://registry.npmjs.org/@aws/lsp-core/-/lsp-core-0.0.12.tgz",
+ "integrity": "sha512-FNbbvzUZQKtUE/LHGsI8b05+M44h47f7ExlET1WeOnhfjDWu7n+wlHRkPZkdsEe1z5pbluGNBaiTO/cV9A2qQQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@aws/language-server-runtimes": "^0.2.109",
+ "@gerhobbelt/gitignore-parser": "^0.2.0-9",
+ "cross-spawn": "7.0.6",
+ "jose": "^5.2.4",
+ "request-light": "^0.8.0",
+ "vscode-languageserver-textdocument": "^1.0.8",
+ "vscode-languageserver-types": "^3.17.3",
+ "vscode-uri": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"server/aws-lsp-yaml": {
"name": "@aws/lsp-yaml",
- "version": "0.1.16",
+ "version": "0.1.17",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
- "@aws/lsp-core": "^0.0.12",
+ "@aws/language-server-runtimes": "^0.2.121",
+ "@aws/lsp-core": "^0.0.13",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-textdocument": "^1.0.8",
"yaml-language-server": "1.13.0"
@@ -29000,7 +29058,7 @@
"name": "@amzn/device-sso-auth-lsp",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"vscode-languageserver": "^9.0.1"
},
"devDependencies": {
@@ -29011,7 +29069,7 @@
"name": "@aws/hello-world-lsp",
"version": "0.0.1",
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"vscode-languageserver": "^9.0.1"
},
"devDependencies": {
diff --git a/server/aws-lsp-antlr4/CHANGELOG.md b/server/aws-lsp-antlr4/CHANGELOG.md
index d3d1442ed6..bde43f2c3c 100644
--- a/server/aws-lsp-antlr4/CHANGELOG.md
+++ b/server/aws-lsp-antlr4/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## [0.1.17](https://github.com/aws/language-servers/compare/lsp-antlr4/v0.1.16...lsp-antlr4/v0.1.17) (2025-08-04)
+
+
+### Bug Fixes
+
+* use new language server runtime ([#2023](https://github.com/aws/language-servers/issues/2023)) ([83ea1e4](https://github.com/aws/language-servers/commit/83ea1e42fe52990696eb9b878fa11e2c5331bec5))
+
+
+### Dependencies
+
+* The following workspace dependencies were updated
+ * dependencies
+ * @aws/lsp-core bumped from ^0.0.12 to ^0.0.13
+
## [0.1.16](https://github.com/aws/language-servers/compare/lsp-antlr4/v0.1.15...lsp-antlr4/v0.1.16) (2025-07-17)
diff --git a/server/aws-lsp-antlr4/package.json b/server/aws-lsp-antlr4/package.json
index 247452e0de..aadfe48b08 100644
--- a/server/aws-lsp-antlr4/package.json
+++ b/server/aws-lsp-antlr4/package.json
@@ -1,6 +1,6 @@
{
"name": "@aws/lsp-antlr4",
- "version": "0.1.16",
+ "version": "0.1.17",
"description": "ANTLR4 language server",
"main": "out/index.js",
"repository": {
@@ -28,8 +28,8 @@
"clean": "rm -rf node_modules"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
- "@aws/lsp-core": "^0.0.12"
+ "@aws/language-server-runtimes": "^0.2.121",
+ "@aws/lsp-core": "^0.0.13"
},
"peerDependencies": {
"antlr4-c3": ">=3.4 < 4",
diff --git a/server/aws-lsp-buildspec/package.json b/server/aws-lsp-buildspec/package.json
index 3fe9054832..92960545fb 100644
--- a/server/aws-lsp-buildspec/package.json
+++ b/server/aws-lsp-buildspec/package.json
@@ -7,7 +7,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-json": "*",
"@aws/lsp-yaml": "*",
"vscode-languageserver": "^9.0.1",
diff --git a/server/aws-lsp-cloudformation/package.json b/server/aws-lsp-cloudformation/package.json
index c9ef37a4ae..2dd24848b8 100644
--- a/server/aws-lsp-cloudformation/package.json
+++ b/server/aws-lsp-cloudformation/package.json
@@ -7,7 +7,7 @@
"compile": "tsc --build"
},
"dependencies": {
- "@aws/language-server-runtimes": "^0.2.120",
+ "@aws/language-server-runtimes": "^0.2.121",
"@aws/lsp-core": "*",
"@aws/lsp-json": "*",
"vscode-languageserver": "^9.0.1",
diff --git a/server/aws-lsp-codewhisperer/CHANGELOG.md b/server/aws-lsp-codewhisperer/CHANGELOG.md
index 762411550a..4d89e37f70 100644
--- a/server/aws-lsp-codewhisperer/CHANGELOG.md
+++ b/server/aws-lsp-codewhisperer/CHANGELOG.md
@@ -1,5 +1,40 @@
# Changelog
+## [0.0.71](https://github.com/aws/language-servers/compare/lsp-codewhisperer/v0.0.70...lsp-codewhisperer/v0.0.71) (2025-08-04)
+
+
+### Features
+
+* adding inline chat telemetry ([#2001](https://github.com/aws/language-servers/issues/2001)) ([8b1c9c7](https://github.com/aws/language-servers/commit/8b1c9c7c3859cdfbbd0abb059066a5c6fe2ffaf2))
+* **amazonq:** implement displayFindings tool ([#2029](https://github.com/aws/language-servers/issues/2029)) ([da11663](https://github.com/aws/language-servers/commit/da1166340f3d13e1d7fd83b260359661443230ea))
+* improve code review tool reliability and error handling ([#2033](https://github.com/aws/language-servers/issues/2033)) ([124244e](https://github.com/aws/language-servers/commit/124244ee7d97adf71a52c4fde7ddb908dbc0bd08))
+* support http transport without authorization for MCP ([97e806c](https://github.com/aws/language-servers/commit/97e806ce7ea5e5be1fd60c4a4d9a54cf76c8f8cb))
+
+
+### Bug Fixes
+
+* add bash command parsing for telemetry metrics ([#2039](https://github.com/aws/language-servers/issues/2039)) ([01d8112](https://github.com/aws/language-servers/commit/01d811225281a2e32f9cd6dab1b575aad8c0b4d6))
+* adding acceptedCharacterCount to UserTriggerDecisionEvent ([#2014](https://github.com/aws/language-servers/issues/2014)) ([3f94486](https://github.com/aws/language-servers/commit/3f944865483a6913138335fe61eee70ae71d7c03))
+* adjust bash command categories ([#2030](https://github.com/aws/language-servers/issues/2030)) ([25ed99f](https://github.com/aws/language-servers/commit/25ed99fcf0eeaa86b0a5e040e90d69becf625c71))
+* adjust cross file context config ([#2011](https://github.com/aws/language-servers/issues/2011)) ([f7ade37](https://github.com/aws/language-servers/commit/f7ade3767e714d5178f24fd9cc90349c5f417979))
+* **amazonq:** fix for mcp server permissions ([#2026](https://github.com/aws/language-servers/issues/2026)) ([89ae720](https://github.com/aws/language-servers/commit/89ae720dc036a90338d192aca801a858e8fa19f8))
+* **amazonq:** fix for mcp server permissions to prefer workspace agent config files ([#2038](https://github.com/aws/language-servers/issues/2038)) ([d2ac614](https://github.com/aws/language-servers/commit/d2ac614f0f16faa8bf689ac9c8bff09d64fc3a3b))
+* **amazonq:** fix processing empty unsupported workspace file ([#2017](https://github.com/aws/language-servers/issues/2017)) ([9e4d0af](https://github.com/aws/language-servers/commit/9e4d0af244b5edba73771b6cb4290d922ef83c43))
+* correct the implementation of gathering open tabs in cross file context ([#2040](https://github.com/aws/language-servers/issues/2040)) ([b7b7a2b](https://github.com/aws/language-servers/commit/b7b7a2bd2020f50069ce89f6505cc2a36b1f3fa7))
+* remove malicious characters from prompt input ([#2009](https://github.com/aws/language-servers/issues/2009)) ([bf8a1e6](https://github.com/aws/language-servers/commit/bf8a1e6136801532132f2bf82def4ca5bf49c82f))
+* sanitize request input ([#2025](https://github.com/aws/language-servers/issues/2025)) ([7c0efd7](https://github.com/aws/language-servers/commit/7c0efd73d5e9a0e3f42d143a10c16782f6e35db8))
+* skip image sanitization ([#2031](https://github.com/aws/language-servers/issues/2031)) ([f02fc23](https://github.com/aws/language-servers/commit/f02fc231136940bd644c426d2b222ae2cba779c4))
+* sometimes Enter does not auto trigger ([#2005](https://github.com/aws/language-servers/issues/2005)) ([c9af035](https://github.com/aws/language-servers/commit/c9af0353a6c6f3b2ca4eead02f19e8ab5ddb8ef1))
+* use fast glob streaming when collecting files ([#2003](https://github.com/aws/language-servers/issues/2003)) ([f7c0a0b](https://github.com/aws/language-servers/commit/f7c0a0b0ef9ce3ecd620acfef00e55745db3d71f))
+* use new language server runtime ([#2023](https://github.com/aws/language-servers/issues/2023)) ([83ea1e4](https://github.com/aws/language-servers/commit/83ea1e42fe52990696eb9b878fa11e2c5331bec5))
+
+
+### Dependencies
+
+* The following workspace dependencies were updated
+ * dependencies
+ * @aws/lsp-core bumped from ^0.0.12 to ^0.0.13
+
## [0.0.70](https://github.com/aws/language-servers/compare/lsp-codewhisperer/v0.0.69...lsp-codewhisperer/v0.0.70) (2025-07-29)
diff --git a/server/aws-lsp-codewhisperer/package.json b/server/aws-lsp-codewhisperer/package.json
index ad0ea87e4e..2c7366ff34 100644
--- a/server/aws-lsp-codewhisperer/package.json
+++ b/server/aws-lsp-codewhisperer/package.json
@@ -1,6 +1,6 @@
{
"name": "@aws/lsp-codewhisperer",
- "version": "0.0.70",
+ "version": "0.0.71",
"description": "CodeWhisperer Language Server",
"main": "out/index.js",
"repository": {
@@ -36,8 +36,8 @@
"@aws-sdk/util-arn-parser": "^3.723.0",
"@aws-sdk/util-retry": "^3.374.0",
"@aws/chat-client-ui-types": "^0.1.56",
- "@aws/language-server-runtimes": "^0.2.120",
- "@aws/lsp-core": "^0.0.12",
+ "@aws/language-server-runtimes": "^0.2.121",
+ "@aws/lsp-core": "^0.0.13",
"@modelcontextprotocol/sdk": "^1.15.0",
"@smithy/node-http-handler": "^2.5.0",
"adm-zip": "^0.5.10",
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts
index 876a199e90..960e3662d0 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/agenticChatController.ts
@@ -124,6 +124,7 @@ import {
isUsageLimitError,
isNullish,
getOriginFromClientInfo,
+ getClientName,
sanitizeInput,
sanitizeRequestInput,
} from '../../shared/utils'
@@ -201,7 +202,10 @@ import { URI } from 'vscode-uri'
import { CommandCategory } from './tools/executeBash'
import { UserWrittenCodeTracker } from '../../shared/userWrittenCodeTracker'
import { CodeReview } from './tools/qCodeAnalysis/codeReview'
-import { FINDINGS_MESSAGE_SUFFIX } from './tools/qCodeAnalysis/codeReviewConstants'
+import {
+ CODE_REVIEW_FINDINGS_MESSAGE_SUFFIX,
+ DISPLAY_FINDINGS_MESSAGE_SUFFIX,
+} from './tools/qCodeAnalysis/codeReviewConstants'
import { McpEventHandler } from './tools/mcp/mcpEventHandler'
import { enabledMCP, createNamespacedToolName } from './tools/mcp/mcpUtils'
import { McpManager } from './tools/mcp/mcpManager'
@@ -225,6 +229,7 @@ import { getLatestAvailableModel } from './utils/agenticChatControllerHelper'
import { ActiveUserTracker } from '../../shared/activeUserTracker'
import { UserContext } from '../../client/token/codewhispererbearertokenclient'
import { CodeWhispererServiceToken } from '../../shared/codeWhispererService'
+import { DisplayFindings } from './tools/qCodeAnalysis/displayFindings'
type ChatHandlers = Omit<
LspHandlers,
@@ -362,7 +367,7 @@ export class AgenticChatController implements ChatHandlers {
this.#features.lsp
)
this.#mcpEventHandler = new McpEventHandler(features, telemetryService)
- this.#origin = getOriginFromClientInfo(this.#features.lsp.getClientInitializeParams()?.clientInfo?.name)
+ this.#origin = getOriginFromClientInfo(getClientName(this.#features.lsp.getClientInitializeParams()))
this.#activeUserTracker = ActiveUserTracker.getInstance(this.#features)
}
@@ -1764,7 +1769,8 @@ export class AgenticChatController implements ChatHandlers {
break
}
case CodeReview.toolName:
- // no need to write tool message for code review
+ case DisplayFindings.toolName:
+ // no need to write tool message for CodeReview or DisplayFindings
break
// — DEFAULT ⇒ Only MCP tools, but can also handle generic tool execution messages
default:
@@ -1940,11 +1946,27 @@ export class AgenticChatController implements ChatHandlers {
) {
await chatResultStream.writeResultBlock({
type: 'tool',
- messageId: toolUse.toolUseId + FINDINGS_MESSAGE_SUFFIX,
+ messageId: toolUse.toolUseId + CODE_REVIEW_FINDINGS_MESSAGE_SUFFIX,
body: (codeReviewResult.output.content as any).findingsByFile,
})
}
break
+ case DisplayFindings.toolName:
+ // no need to write tool result for code review, this is handled by model via chat
+ // Push result in message so that it is picked by IDE plugin to show in issues panel
+ const displayFindingsResult = result as InvokeOutput
+ if (
+ displayFindingsResult?.output?.kind === 'json' &&
+ displayFindingsResult.output.success &&
+ displayFindingsResult.output.content !== undefined
+ ) {
+ await chatResultStream.writeResultBlock({
+ type: 'tool',
+ messageId: toolUse.toolUseId + DISPLAY_FINDINGS_MESSAGE_SUFFIX,
+ body: JSON.stringify(displayFindingsResult.output.content),
+ })
+ }
+ break
// — DEFAULT ⇒ MCP tools
default:
await this.#handleMcpToolResult(toolUse, result, session, chatResultStream)
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/executeBash.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/executeBash.ts
index 807379268f..1ad52e664e 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/executeBash.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/executeBash.ts
@@ -12,6 +12,8 @@ import { Features } from '../../types'
import { getWorkspaceFolderPaths } from '@aws/lsp-core/out/util/workspaceUtils'
// eslint-disable-next-line import/no-nodejs-modules
import { existsSync, statSync } from 'fs'
+import { parseBaseCommands } from '../utils/commandParser'
+import { BashCommandEvent, ChatTelemetryEventName } from '../../../shared/telemetry/types'
export enum CommandCategory {
ReadOnly,
@@ -130,9 +132,15 @@ export class ExecuteBash {
private childProcess?: ChildProcess
private readonly logging: Features['logging']
private readonly workspace: Features['workspace']
- constructor(features: Pick & Partial) {
+ private readonly telemetry: Features['telemetry']
+ private readonly credentialsProvider: Features['credentialsProvider']
+ constructor(
+ features: Pick & Partial
+ ) {
this.logging = features.logging
this.workspace = features.workspace
+ this.telemetry = features.telemetry
+ this.credentialsProvider = features.credentialsProvider
}
public async validate(input: ExecuteBashParams): Promise {
@@ -567,6 +575,7 @@ export class ExecuteBash {
})
}
+ let success = false
try {
const result = await this.childProcess.run()
@@ -579,7 +588,7 @@ export class ExecuteBash {
const exitStatus = result.exitCode ?? 0
const stdout = stdoutBuffer.join('\n')
const stderr = stderrBuffer.join('\n')
- const success = exitStatus === 0 && !stderr
+ success = exitStatus === 0 && !stderr
const [stdoutTrunc, stdoutSuffix] = ExecuteBash.truncateSafelyWithSuffix(
stdout,
maxToolResponseSize / 3
@@ -611,6 +620,25 @@ export class ExecuteBash {
reject(new Error(`Failed to execute command: ${err.message}`))
}
} finally {
+ // Extract individual base commands for telemetry purposes
+ const args = split(params.command)
+ const baseCommands = parseBaseCommands(args)
+ baseCommands.forEach(command => {
+ const metricPayload = {
+ name: ChatTelemetryEventName.BashCommand,
+ data: {
+ credentialStartUrl: this.credentialsProvider.getConnectionMetadata()?.sso?.startUrl,
+ result: cancellationToken?.isCancellationRequested
+ ? 'Cancelled'
+ : success
+ ? 'Succeeded'
+ : 'Failed',
+ command: command,
+ } as BashCommandEvent,
+ }
+ this.telemetry.emitMetric(metricPayload)
+ })
+
await writer?.close()
writer?.releaseLock()
}
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/mcp/mcpEventHandler.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/mcp/mcpEventHandler.ts
index b4ca871dc6..22befec0cd 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/mcp/mcpEventHandler.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/mcp/mcpEventHandler.ts
@@ -1145,14 +1145,17 @@ export class McpEventHandler {
try {
// Skip server config check for Built-in server
+ const serverConfig = McpManager.instance.getAllServerConfigs().get(serverName)
if (serverName !== 'Built-in') {
- const serverConfig = McpManager.instance.getAllServerConfigs().get(serverName)
if (!serverConfig) {
throw new Error(`Server '${serverName}' not found`)
}
}
- const mcpServerPermission = await this.#processPermissionUpdates(updatedPermissionConfig)
+ const mcpServerPermission = await this.#processPermissionUpdates(
+ updatedPermissionConfig,
+ serverConfig?.__configPath__
+ )
// Store the permission config instead of applying it immediately
this.#pendingPermissionConfig = {
@@ -1347,10 +1350,7 @@ export class McpEventHandler {
/**
* Processes permission updates from the UI
*/
- async #processPermissionUpdates(updatedPermissionConfig: any) {
- // Get the appropriate agent path
- const agentPath = await this.#getAgentPath()
-
+ async #processPermissionUpdates(updatedPermissionConfig: any, agentPath: string | undefined) {
const perm: MCPServerPermission = {
enabled: true,
toolPerms: {},
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewConstants.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewConstants.ts
index e960fd0596..bc7070ab1f 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewConstants.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewConstants.ts
@@ -211,6 +211,7 @@ export const SKIP_DIRECTORIES = [
'temp',
]
-export const FINDINGS_MESSAGE_SUFFIX = '_codeReviewFindings'
+export const CODE_REVIEW_FINDINGS_MESSAGE_SUFFIX = '_codeReviewFindings'
+export const DISPLAY_FINDINGS_MESSAGE_SUFFIX = '_displayFindings'
export const CODE_REVIEW_METRICS_PARENT_NAME = 'amazonq_codeReviewTool'
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewUtils.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewUtils.ts
index 1b94770746..5b6d562f7d 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewUtils.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/codeReviewUtils.ts
@@ -278,6 +278,18 @@ export class CodeReviewUtils {
return qCapabilities?.codeReviewInChat || false
}
+ /**
+ * Check if storing display findings in the Code Issues panel is enabled.
+ * @param params Initialize parameters from client
+ * @returns True if display findings is enabled, false otherwise
+ */
+ public static isDisplayFindingsEnabled(params: InitializeParams | undefined): boolean {
+ const qCapabilities = params?.initializationOptions?.aws?.awsClientCapabilities?.q as
+ | QClientCapabilities
+ | undefined
+ return qCapabilities?.displayFindings || false
+ }
+
/**
* Converts a Windows absolute file path to Unix format and removes the drive letter
* @param windowsPath The Windows path to convert
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindings.test.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindings.test.ts
new file mode 100644
index 0000000000..e541a76026
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindings.test.ts
@@ -0,0 +1,394 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DisplayFindings } from './displayFindings'
+import { DISPLAY_FINDINGS_TOOL_NAME } from './displayFindingsConstants'
+import * as sinon from 'sinon'
+import * as path from 'path'
+import { expect } from 'chai'
+import { CancellationError } from '@aws/lsp-core'
+import { CodeReviewFinding } from './codeReviewTypes'
+import { Features } from '@aws/language-server-runtimes/server-interface/server'
+
+describe('DisplayFindings', () => {
+ let sandbox: sinon.SinonSandbox
+ let displayFindings: DisplayFindings
+ let mockFeatures: Pick & Partial
+ let mockCancellationToken: { isCancellationRequested: boolean }
+ let mockWritableStream: { getWriter: sinon.SinonStub }
+ let mockWriter: {
+ write: sinon.SinonStub
+ close: sinon.SinonStub
+ releaseLock: sinon.SinonStub
+ }
+
+ let CODE_REVIEW_FINDING_1: CodeReviewFinding
+
+ let CODE_REVIEW_FINDING_2: CodeReviewFinding
+
+ let INPUT_FINDING_1: {
+ filePath: string
+ startLine: string
+ endLine: string
+ title: string
+ description: string
+ severity: string
+ language: string
+ }
+
+ let INPUT_FINDING_2: {
+ filePath: string
+ startLine: string
+ endLine: string
+ title: string
+ description: string
+ severity: string
+ language: string
+ }
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox()
+
+ mockWriter = {
+ write: sandbox.stub().resolves(),
+ close: sandbox.stub().resolves(),
+ releaseLock: sandbox.stub(),
+ }
+
+ mockWritableStream = {
+ getWriter: sandbox.stub().returns(mockWriter),
+ }
+
+ mockCancellationToken = {
+ isCancellationRequested: false,
+ }
+
+ mockFeatures = {
+ logging: {
+ info: sandbox.stub(),
+ warn: sandbox.stub(),
+ error: sandbox.stub(),
+ debug: sandbox.stub(),
+ log: sandbox.stub(),
+ },
+ telemetry: {
+ emitMetric: sandbox.stub(),
+ onClientTelemetry: sandbox.stub(),
+ },
+ workspace: {
+ getTextDocument: sandbox.stub(),
+ getAllTextDocuments: sandbox.stub(),
+ getWorkspaceFolder: sandbox.stub(),
+ getAllWorkspaceFolders: sandbox.stub(),
+ fs: {
+ copyFile: sandbox.stub(),
+ exists: sandbox.stub(),
+ getFileSize: sandbox.stub(),
+ getServerDataDirPath: sandbox.stub(),
+ getTempDirPath: sandbox.stub(),
+ getUserHomeDir: sandbox.stub(),
+ readdir: sandbox.stub(),
+ readFile: sandbox.stub(),
+ isFile: sandbox.stub(),
+ rm: sandbox.stub(),
+ writeFile: sandbox.stub(),
+ appendFile: sandbox.stub(),
+ mkdir: sandbox.stub(),
+ readFileSync: sandbox.stub(),
+ },
+ },
+ }
+
+ displayFindings = new DisplayFindings(mockFeatures)
+
+ CODE_REVIEW_FINDING_1 = {
+ filePath: '/test/file1.js',
+ startLine: 10,
+ endLine: 15,
+ title: 'Issue 1',
+ comment: 'Description 1',
+ description: { text: 'Description 1', markdown: 'Description 1' },
+ severity: 'High',
+ language: 'javascript',
+ detectorName: 'DisplayFindings',
+ detectorId: '',
+ findingId: '',
+ relatedVulnerabilities: [],
+ recommendation: { text: '' },
+ suggestedFixes: [],
+ scanJobId: '',
+ autoDetected: false,
+ findingContext: undefined,
+ }
+
+ CODE_REVIEW_FINDING_2 = {
+ filePath: '/test/file2.py',
+ startLine: 5,
+ endLine: 10,
+ title: 'Issue 2',
+ comment: 'Description 2',
+ description: { text: 'Description 2', markdown: 'Description 2' },
+ severity: 'Low',
+ language: 'python',
+ detectorName: 'DisplayFindings',
+ detectorId: '',
+ findingId: '',
+ relatedVulnerabilities: [],
+ recommendation: { text: '' },
+ suggestedFixes: [],
+ scanJobId: '',
+ autoDetected: false,
+ findingContext: undefined,
+ }
+
+ INPUT_FINDING_1 = {
+ filePath: '/test/file1.js',
+ startLine: '10',
+ endLine: '15',
+ title: 'Issue 1',
+ description: 'Description 1',
+ severity: 'High',
+ language: 'javascript',
+ }
+
+ INPUT_FINDING_2 = {
+ filePath: '/test/file2.py',
+ startLine: '5',
+ endLine: '10',
+ title: 'Issue 2',
+ description: 'Description 2',
+ severity: 'Low',
+ language: 'python',
+ }
+ })
+
+ afterEach(() => {
+ sandbox.restore()
+ })
+
+ describe('static properties', () => {
+ it('should have correct tool name', () => {
+ expect(DisplayFindings.toolName).to.equal(DISPLAY_FINDINGS_TOOL_NAME)
+ })
+
+ it('should have tool description', () => {
+ expect(DisplayFindings.toolDescription).to.be.a('string')
+ })
+
+ it('should have input schema', () => {
+ expect(DisplayFindings.inputSchema).to.be.an('object')
+ })
+ })
+
+ describe('execute', () => {
+ let context: any
+ let validInput: any
+
+ beforeEach(() => {
+ context = {
+ cancellationToken: mockCancellationToken,
+ writableStream: mockWritableStream,
+ }
+
+ validInput = {
+ findings: [INPUT_FINDING_1],
+ }
+ })
+
+ it('should execute successfully with valid input', async () => {
+ const result = await displayFindings.execute(validInput, context)
+
+ expect(result.output.success).to.be.true
+ expect(result.output.kind).to.equal('json')
+ expect(result.output.content).to.be.an('array')
+ expect(result.output.content).to.have.length(1)
+ expect((result.output.content as any)[0].filePath).to.equal(path.normalize('/test/file1.js'))
+ expect((result.output.content as any)[0].issues).to.have.length(1)
+ })
+
+ it('should handle multiple findings for same file', async () => {
+ INPUT_FINDING_2.filePath = '/test/file1.js'
+ const inputWithMultipleFindings = {
+ findings: [INPUT_FINDING_1, INPUT_FINDING_2],
+ }
+
+ const result = await displayFindings.execute(inputWithMultipleFindings, context)
+
+ expect(result.output.success).to.be.true
+ expect(result.output.content).to.have.length(1)
+ expect((result.output.content as any)[0].issues).to.have.length(2)
+ })
+
+ it('should handle findings for different files', async () => {
+ const inputWithDifferentFiles = {
+ findings: [INPUT_FINDING_1, INPUT_FINDING_2],
+ }
+
+ const result = await displayFindings.execute(inputWithDifferentFiles, context)
+
+ expect(result.output.success).to.be.true
+ expect(result.output.content).to.have.length(2)
+ expect((result.output.content as any)[0].issues).to.have.length(1)
+ expect((result.output.content as any)[1].issues).to.have.length(1)
+ })
+
+ it('should handle empty findings array', async () => {
+ const emptyInput = { findings: [] }
+
+ const result = await displayFindings.execute(emptyInput, context)
+
+ expect(result.output.success).to.be.true
+ expect(result.output.content).to.be.an('array')
+ expect(result.output.content).to.have.length(0)
+ })
+
+ it('should handle invalid input schema', async () => {
+ const invalidInput = {
+ findings: [
+ {
+ filePath: '/test/file.js',
+ // Missing required fields
+ },
+ ],
+ }
+
+ try {
+ await displayFindings.execute(invalidInput, context)
+ expect.fail('Expected validation error')
+ } catch (error) {
+ expect(error).to.be.instanceOf(Error)
+ }
+ })
+
+ it('should handle cancellation', async () => {
+ mockCancellationToken.isCancellationRequested = true
+
+ try {
+ await displayFindings.execute(validInput, context)
+ expect.fail('Expected cancellation error')
+ } catch (error) {
+ expect(error).to.be.instanceOf(CancellationError)
+ }
+ })
+
+ it('should handle unexpected errors gracefully', async () => {
+ // Make validateInputAndSetup throw an error
+ sandbox.stub(displayFindings as any, 'validateInputAndSetup').rejects(new Error('Unexpected error'))
+
+ try {
+ await displayFindings.execute(validInput, context)
+ expect.fail('Expected error to be thrown')
+ } catch (error: any) {
+ expect(error.message).to.equal('Unexpected error')
+ }
+ })
+ })
+
+ describe('validateInputAndSetup', () => {
+ it('should validate and setup correctly', async () => {
+ const input = {
+ findings: [INPUT_FINDING_1],
+ }
+
+ const context = {
+ cancellationToken: mockCancellationToken,
+ writableStream: mockWritableStream,
+ }
+
+ const result = await (displayFindings as any).validateInputAndSetup(input, context)
+
+ expect(result).to.be.an('array')
+ expect(result).to.have.length(1)
+ expect(result[0].filePath).to.equal('/test/file1.js')
+ })
+ })
+
+ describe('mapToCodeReviewFinding', () => {
+ it('should map DisplayFinding to CodeReviewFinding correctly', () => {
+ const displayFinding = {
+ filePath: '/test/file.js',
+ startLine: '10',
+ endLine: '15',
+ title: 'Test Issue',
+ description: 'Test description',
+ severity: 'High',
+ language: 'javascript',
+ suggestedFixes: ['Fix suggestion'],
+ }
+
+ const result = (displayFindings as any).mapToCodeReviewFinding(displayFinding)
+
+ expect(result.filePath).to.equal('/test/file.js')
+ expect(result.startLine).to.equal(10)
+ expect(result.endLine).to.equal(15)
+ expect(result.title).to.equal('Test Issue')
+ expect(result.comment).to.equal('Test description')
+ expect(result.severity).to.equal('High')
+ expect(result.language).to.equal('javascript')
+ expect(result.suggestedFixes).to.deep.equal(['Fix suggestion'])
+ expect(result.detectorName).to.equal('DisplayFindings')
+ expect(result.autoDetected).to.be.false
+ })
+
+ it('should handle missing suggestedFixes', () => {
+ const displayFinding = {
+ filePath: '/test/file.js',
+ startLine: '10',
+ endLine: '15',
+ title: 'Test Issue',
+ description: 'Test description',
+ severity: 'High',
+ language: 'javascript',
+ }
+
+ const result = (displayFindings as any).mapToCodeReviewFinding(displayFinding)
+
+ expect(result.suggestedFixes).to.deep.equal([])
+ })
+ })
+
+ describe('aggregateFindingsByFile', () => {
+ it('should aggregate findings by file path', () => {
+ CODE_REVIEW_FINDING_2.filePath = '/test/file1.js'
+ const findings = [CODE_REVIEW_FINDING_1, CODE_REVIEW_FINDING_2]
+
+ const result = (displayFindings as any).aggregateFindingsByFile(findings)
+
+ expect(result).to.have.length(1)
+ expect(result[0].filePath).to.equal(path.normalize('/test/file1.js'))
+ expect(result[0].issues).to.have.length(2)
+ })
+
+ it('should handle findings from different files', () => {
+ const findings = [CODE_REVIEW_FINDING_1, CODE_REVIEW_FINDING_2]
+
+ const result = (displayFindings as any).aggregateFindingsByFile(findings)
+
+ expect(result).to.have.length(2)
+ expect(result[0].issues).to.have.length(1)
+ expect(result[1].issues).to.have.length(1)
+ })
+ })
+
+ describe('checkCancellation', () => {
+ it('should not throw when cancellation is not requested', () => {
+ mockCancellationToken.isCancellationRequested = false
+ ;(displayFindings as any).cancellationToken = mockCancellationToken
+
+ expect(() => {
+ ;(displayFindings as any).checkCancellation()
+ }).to.not.throw()
+ })
+
+ it('should throw CancellationError when cancellation is requested', () => {
+ mockCancellationToken.isCancellationRequested = true
+ ;(displayFindings as any).cancellationToken = mockCancellationToken
+
+ expect(() => {
+ ;(displayFindings as any).checkCancellation()
+ }).to.throw(CancellationError)
+ })
+ })
+})
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindings.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindings.ts
new file mode 100644
index 0000000000..5a5c88d67a
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindings.ts
@@ -0,0 +1,166 @@
+/* eslint-disable import/no-nodejs-modules */
+
+import { Features } from '@aws/language-server-runtimes/server-interface/server'
+import { DISPLAY_FINDINGS_TOOL_NAME, DISPLAY_FINDINGS_TOOL_DESCRIPTION } from './displayFindingsConstants'
+import { CodeReviewUtils } from './codeReviewUtils'
+import { DISPLAY_FINDINGS_INPUT_SCHEMA, Z_DISPLAY_FINDINGS_INPUT_SCHEMA } from './displayFindingsSchemas'
+import { CancellationToken } from '@aws/language-server-runtimes/server-interface'
+import { InvokeOutput } from '../toolShared'
+import { CancellationError } from '@aws/lsp-core'
+import { DisplayFinding, FailedMetricName, SuccessMetricName } from './displayFindingsTypes'
+import { CodeReviewFinding } from './codeReviewTypes'
+import * as path from 'path'
+import { DisplayFindingsUtils } from './displayFindingsUtils'
+
+export class DisplayFindings {
+ private readonly logging: Features['logging']
+ private readonly telemetry: Features['telemetry']
+ private readonly workspace: Features['workspace']
+ private cancellationToken?: CancellationToken
+ private writableStream?: WritableStream
+
+ constructor(features: Pick & Partial) {
+ this.logging = features.logging
+ this.telemetry = features.telemetry
+ this.workspace = features.workspace
+ }
+
+ static readonly toolName = DISPLAY_FINDINGS_TOOL_NAME
+
+ static readonly toolDescription = DISPLAY_FINDINGS_TOOL_DESCRIPTION
+
+ static readonly inputSchema = DISPLAY_FINDINGS_INPUT_SCHEMA
+
+ /**
+ * Main execution method for the displayFindings tool
+ * @param input User input parameters for display findings
+ * @param context Execution context containing clients and tokens
+ * @returns Output containing code review results or error message
+ */
+ public async execute(input: any, context: any): Promise {
+ let chatStreamWriter: WritableStreamDefaultWriter | undefined
+
+ try {
+ this.logging.info(`Executing ${DISPLAY_FINDINGS_TOOL_NAME}: ${JSON.stringify(input)}`)
+
+ // 1. Validate input
+ const setup = await this.validateInputAndSetup(input, context)
+ this.checkCancellation()
+
+ // 2. group the findings into AggregatedCodeScanIssue
+ const mappedFindings = setup.map(finding => this.mapToCodeReviewFinding(finding))
+ const aggregatedFindings = this.aggregateFindingsByFile(mappedFindings)
+
+ DisplayFindingsUtils.emitMetric(
+ {
+ reason: SuccessMetricName.DisplayFindingsSuccess,
+ result: 'Succeeded',
+ metadata: {
+ findingsCount: setup.length,
+ },
+ },
+ this.logging,
+ this.telemetry
+ )
+
+ return {
+ output: {
+ kind: 'json',
+ success: true,
+ content: aggregatedFindings,
+ },
+ }
+ } catch (error: any) {
+ if (error instanceof CancellationError) {
+ throw error
+ }
+
+ DisplayFindingsUtils.emitMetric(
+ {
+ reason: FailedMetricName.DisplayFindingsFailed,
+ result: 'Failed',
+ reasonDesc: error,
+ },
+ this.logging,
+ this.telemetry
+ )
+
+ throw new Error(error.message)
+ } finally {
+ await chatStreamWriter?.close()
+ chatStreamWriter?.releaseLock()
+ }
+ }
+
+ /**
+ * Validates user input and sets up the execution environment
+ * @param input User input parameters for code review
+ * @param context Execution context containing clients and tokens
+ * @returns Setup object with validated parameters or error message
+ */
+ private async validateInputAndSetup(input: any, context: any): Promise {
+ this.cancellationToken = context.cancellationToken as CancellationToken
+
+ this.writableStream = context.writableStream as WritableStream
+
+ // parse input
+ const validatedInput = Z_DISPLAY_FINDINGS_INPUT_SCHEMA.parse(input)
+
+ return validatedInput.findings as DisplayFinding[]
+ }
+
+ private mapToCodeReviewFinding(finding: DisplayFinding): CodeReviewFinding {
+ return {
+ filePath: finding.filePath,
+ startLine: parseInt(finding.startLine),
+ endLine: parseInt(finding.endLine),
+ comment: finding.description,
+ title: finding.title,
+ description: { markdown: finding.description, text: finding.description },
+ detectorId: '',
+ detectorName: 'DisplayFindings',
+ findingId: '',
+ relatedVulnerabilities: [],
+ severity: finding.severity,
+ recommendation: { text: '' },
+ suggestedFixes: finding.suggestedFixes ?? [],
+ scanJobId: '',
+ language: finding.language,
+ autoDetected: false,
+ findingContext: undefined,
+ }
+ }
+
+ private aggregateFindingsByFile(
+ findings: CodeReviewFinding[]
+ ): { filePath: string; issues: CodeReviewFinding[] }[] {
+ const aggregatedCodeScanIssueMap = new Map()
+
+ for (const finding of findings) {
+ const resolvedPath = path.normalize(finding.filePath)
+ if (resolvedPath) {
+ if (aggregatedCodeScanIssueMap.has(resolvedPath)) {
+ aggregatedCodeScanIssueMap.get(resolvedPath)?.push(finding)
+ } else {
+ aggregatedCodeScanIssueMap.set(resolvedPath, [finding])
+ }
+ } else {
+ this.logging.warn(`Could not resolve finding file path: ${finding.filePath}`)
+ }
+ }
+
+ return Array.from(aggregatedCodeScanIssueMap.entries()).map(([filePath, issues]) => ({
+ filePath,
+ issues,
+ }))
+ }
+
+ /**
+ * Checks if the operation has been cancelled by the user
+ * @param message Optional message to include in the cancellation error
+ * @throws Error if the operation has been cancelled
+ */
+ private checkCancellation(message: string = 'Command execution cancelled'): void {
+ CodeReviewUtils.checkCancellation(this.cancellationToken, this.logging, message)
+ }
+}
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsConstants.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsConstants.ts
new file mode 100644
index 0000000000..855f29e642
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsConstants.ts
@@ -0,0 +1,40 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * Tool name for DisplayFindings
+ */
+export const DISPLAY_FINDINGS_TOOL_NAME = 'displayFindings'
+
+/**
+ * Tool description for DisplayFindings
+ */
+export const DISPLAY_FINDINGS_TOOL_DESCRIPTION = [
+ 'The displayFindings tool displays code issues, bugs, security vulnerabilities, and code quality violations in the Code Issues Panel.',
+ '',
+ '**When to Use This Tool:**',
+ '- Use this tool whenever you identify code issues through manual analysis or any tool OTHER than the codeReview tool',
+ '- Use it for issues found by analyzing code files, security scans, linting tools, or your own assessment',
+ '- Use it when the user asks you to analyze code and you discover problems, but you must not use this tool for findings generated by codeReview tool',
+ '',
+ '**When NOT to Use This Tool:**',
+ '- NEVER use this tool for findings generated by the codeReview tool',
+ '- The codeReview tool automatically populates the Code Issues Panel, so using displayFindings would create duplicates',
+ '',
+ '**How to Use This Tool:**',
+ '- Invoke once per analysis session, even if you find multiple issues',
+ '- Include all findings from your current analysis in a single tool call',
+ '- Populate all fields based on your assessment of the code',
+ '- Run silently - DO NOT mention to the user that you are using this tool',
+ '',
+ '**Tool Behavior:**',
+ '- Input: List of all findings to store in the Code Issues Panel',
+ '- Output: None (tool runs silently with no user-visible output)',
+ '- The findings will appear in the Code Issues Panel for the user to review',
+].join('\n')
+
+export const FINDINGS_MESSAGE_SUFFIX = '_displayFindings'
+
+export const DISPLAY_FINDINGS_METRICS_PARENT_NAME = 'amazonq_displayFindingsTool'
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsSchemas.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsSchemas.ts
new file mode 100644
index 0000000000..63dcdad66e
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsSchemas.ts
@@ -0,0 +1,95 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { z } from 'zod'
+import { FINDING_SEVERITY } from './codeReviewConstants'
+
+/**
+ * Input schema for CodeReview tool
+ */
+export const DISPLAY_FINDINGS_INPUT_SCHEMA = {
+ type: 'object',
+ description: [
+ 'There is only one input to the DisplayFindings tool: the findings.',
+ 'Please format all of the findings which are meant to be displayed using this schema.',
+ ].join('\n'),
+ properties: {
+ findings: {
+ type: 'array',
+ description: [
+ 'Array of the code issues, bugs, security risks, and code quality violations which were found by the agent and need to be sent to the Code Issues Panel',
+ ].join('\n'),
+ items: {
+ type: 'object',
+ description: 'Array item containing all of the findings which will be sent to the Code Issues Panel',
+ properties: {
+ filePath: {
+ type: 'string',
+ description: 'The absolute path of the file which has the finding',
+ },
+ startLine: {
+ type: 'string',
+ description: 'The line number of the first line of the finding',
+ },
+ endLine: {
+ type: 'string',
+ description: 'The line number of the last line of the finding.',
+ },
+ title: {
+ type: 'string',
+ description: 'A short title to represent the finding',
+ },
+ language: {
+ type: 'string',
+ description: 'The programming language of the file which holds the finding',
+ },
+ description: {
+ type: 'string',
+ description: 'A more thorough description of the finding',
+ },
+ severity: {
+ type: 'string',
+ description: 'The severity of the finding',
+ enum: FINDING_SEVERITY,
+ },
+ suggestedFixes: {
+ type: 'array',
+ description:
+ 'An array of possible fixes. Do not generate fixes just to populate this, only include them if they are provided.',
+ items: {
+ type: 'string',
+ description: 'The absolute path of the file which has the finding',
+ },
+ },
+ },
+ required: ['filePath', 'startLine', 'endLine', 'title', 'severity', 'description', 'language'] as const,
+ },
+ },
+ },
+ required: ['findings'] as const,
+}
+
+/**
+ * Schema for a single finding
+ */
+export const Z_DISPLAY_FINDING_SCHEMA = z.object({
+ description: z.string(),
+ endLine: z.string(),
+ filePath: z.string(),
+ language: z.string(),
+ severity: z.enum(FINDING_SEVERITY as [string, ...string[]]),
+ startLine: z.string(),
+ suggestedFixes: z.array(z.string().optional()).optional(),
+ title: z.string(),
+})
+
+/**
+ * Schema for an array of findings
+ */
+export const Z_DISPLAY_FINDINGS_SCHEMA = z.array(Z_DISPLAY_FINDING_SCHEMA)
+
+export const Z_DISPLAY_FINDINGS_INPUT_SCHEMA = z.object({
+ findings: Z_DISPLAY_FINDINGS_SCHEMA,
+})
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsTypes.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsTypes.ts
new file mode 100644
index 0000000000..2c8ae1024f
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsTypes.ts
@@ -0,0 +1,32 @@
+export type DisplayFinding = {
+ filePath: string
+ startLine: string
+ endLine: string
+ comment: string
+ title: string
+ description: string
+ severity: string
+ suggestedFixes: (string | undefined)[] | undefined
+ language: string
+}
+
+export enum SuccessMetricName {
+ DisplayFindingsSuccess = 'displayFindingsSuccess',
+}
+
+export enum FailedMetricName {
+ DisplayFindingsFailed = 'displayFindingsFailed',
+}
+
+export type DisplayFindingsMetric =
+ | {
+ reason: SuccessMetricName
+ result: 'Succeeded'
+ metadata?: object
+ }
+ | {
+ reason: FailedMetricName
+ result: 'Failed'
+ reasonDesc: string
+ metadata?: object
+ }
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsUtils.test.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsUtils.test.ts
new file mode 100644
index 0000000000..4921d4d067
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsUtils.test.ts
@@ -0,0 +1,103 @@
+/*!
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { DisplayFindingsUtils } from './displayFindingsUtils'
+import { SuccessMetricName, FailedMetricName, DisplayFindingsMetric } from './displayFindingsTypes'
+import { Features } from '@aws/language-server-runtimes/server-interface/server'
+import * as sinon from 'sinon'
+import { expect } from 'chai'
+
+describe('DisplayFindingsUtils', () => {
+ let sandbox: sinon.SinonSandbox
+
+ const mockLogging = {
+ log: sinon.stub(),
+ info: sinon.stub(),
+ warn: sinon.stub(),
+ error: sinon.stub(),
+ debug: sinon.stub(),
+ }
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox()
+ mockLogging.info.reset()
+ mockLogging.warn.reset()
+ mockLogging.error.reset()
+ mockLogging.debug.reset()
+ })
+
+ afterEach(() => {
+ sandbox.restore()
+ })
+
+ describe('emitMetric', () => {
+ let mockTelemetry: Features['telemetry']
+
+ beforeEach(() => {
+ mockTelemetry = {
+ emitMetric: sinon.stub(),
+ } as unknown as Features['telemetry']
+ })
+
+ it('should emit a success metric with metadata', () => {
+ const metric = {
+ reason: SuccessMetricName.DisplayFindingsSuccess,
+ result: 'Succeeded',
+ metadata: { findingsCount: 5 },
+ } as DisplayFindingsMetric
+
+ DisplayFindingsUtils.emitMetric(metric, mockLogging, mockTelemetry)
+
+ sinon.assert.calledWith(mockTelemetry.emitMetric as sinon.SinonStub, {
+ name: 'amazonq_displayFindingsTool',
+ data: {
+ findingsCount: 5,
+ reason: 'displayFindingsSuccess',
+ result: 'Succeeded',
+ },
+ })
+
+ sinon.assert.calledWith(mockLogging.info, sinon.match(/Emitting telemetry metric: displayFindingsSuccess/))
+ })
+
+ it('should emit a failure metric with reasonDesc', () => {
+ const metric = {
+ reason: FailedMetricName.DisplayFindingsFailed,
+ result: 'Failed',
+ reasonDesc: 'Validation failed',
+ metadata: { errorType: 'validation' },
+ } as DisplayFindingsMetric
+
+ DisplayFindingsUtils.emitMetric(metric, mockLogging, mockTelemetry)
+
+ sinon.assert.calledWith(mockTelemetry.emitMetric as sinon.SinonStub, {
+ name: 'amazonq_displayFindingsTool',
+ data: {
+ errorType: 'validation',
+ reason: 'displayFindingsFailed',
+ result: 'Failed',
+ reasonDesc: 'Validation failed',
+ },
+ })
+ })
+
+ it('should handle metrics without metadata', () => {
+ const metric = {
+ reason: SuccessMetricName.DisplayFindingsSuccess,
+ result: 'Succeeded',
+ } as DisplayFindingsMetric
+
+ DisplayFindingsUtils.emitMetric(metric, mockLogging, mockTelemetry)
+
+ sinon.assert.calledWith(mockTelemetry.emitMetric as sinon.SinonStub, {
+ name: 'amazonq_displayFindingsTool',
+ data: {
+ reason: 'displayFindingsSuccess',
+ result: 'Succeeded',
+ },
+ })
+ })
+ })
+})
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsUtils.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsUtils.ts
new file mode 100644
index 0000000000..a493f7165a
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/qCodeAnalysis/displayFindingsUtils.ts
@@ -0,0 +1,34 @@
+import { Features } from '@aws/language-server-runtimes/server-interface/server'
+import { DISPLAY_FINDINGS_METRICS_PARENT_NAME } from './displayFindingsConstants'
+import { DisplayFindingsMetric } from './displayFindingsTypes'
+/**
+ * Utility functions for DisplayFindings
+ */
+export class DisplayFindingsUtils {
+ /**
+ * Emit a telemetry metric with standard formatting
+ * @param metricSuffix Suffix for the metric name
+ * @param metricData Additional metric data
+ * @param toolName Tool name for the metric prefix
+ * @param logging Logging interface
+ * @param telemetry Telemetry interface
+ * @param credentialStartUrl Optional credential start URL
+ */
+ public static emitMetric(
+ metric: DisplayFindingsMetric,
+ logging: Features['logging'],
+ telemetry: Features['telemetry']
+ ): void {
+ const { metadata, ...metricDetails } = metric
+ const metricPayload = {
+ name: DISPLAY_FINDINGS_METRICS_PARENT_NAME,
+ data: {
+ // metadata is optional attribute
+ ...(metadata || {}),
+ ...metricDetails,
+ },
+ }
+ logging.info(`Emitting telemetry metric: ${metric.reason} with data: ${JSON.stringify(metricPayload.data)}`)
+ telemetry.emitMetric(metricPayload)
+ }
+}
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/toolServer.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/toolServer.ts
index c49927eefd..54eba4c852 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/toolServer.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/toolServer.ts
@@ -23,6 +23,7 @@ import {
import { FsReplace, FsReplaceParams } from './fsReplace'
import { CodeReviewUtils } from './qCodeAnalysis/codeReviewUtils'
import { DEFAULT_AWS_Q_ENDPOINT_URL, DEFAULT_AWS_Q_REGION } from '../../../shared/constants'
+import { DisplayFindings } from './qCodeAnalysis/displayFindings'
export const FsToolsServer: Server = ({ workspace, logging, agent, lsp }) => {
const fsReadTool = new FsRead({ workspace, lsp, logging })
@@ -102,6 +103,12 @@ export const QCodeAnalysisServer: Server = ({
workspace,
})
+ const displayFindingsTool = new DisplayFindings({
+ logging,
+ telemetry,
+ workspace,
+ })
+
lsp.onInitialized(async () => {
if (!CodeReviewUtils.isAgenticReviewEnabled(lsp.getClientInitializeParams())) {
logging.warn('Agentic Review is currently not supported')
@@ -140,13 +147,33 @@ export const QCodeAnalysisServer: Server = ({
},
ToolClassification.BuiltIn
)
+
+ if (!CodeReviewUtils.isDisplayFindingsEnabled(lsp.getClientInitializeParams())) {
+ logging.warn('Display Findings is currently not supported')
+ return
+ }
+
+ agent.addTool(
+ {
+ name: DisplayFindings.toolName,
+ description: DisplayFindings.toolDescription,
+ inputSchema: DisplayFindings.inputSchema,
+ },
+ async (input: any, token?: CancellationToken, updates?: WritableStream) => {
+ return await displayFindingsTool.execute(input, {
+ cancellationToken: token,
+ writableStream: updates,
+ })
+ },
+ ToolClassification.BuiltIn
+ )
})
return () => {}
}
-export const BashToolsServer: Server = ({ logging, workspace, agent, lsp }) => {
- const bashTool = new ExecuteBash({ logging, workspace, lsp })
+export const BashToolsServer: Server = ({ logging, workspace, agent, lsp, telemetry, credentialsProvider }) => {
+ const bashTool = new ExecuteBash({ logging, workspace, lsp, telemetry, credentialsProvider })
agent.addTool(
bashTool.getSpec(),
async (input: ExecuteBashParams, token?: CancellationToken, updates?: WritableStream) => {
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/utils/commandParser.test.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/utils/commandParser.test.ts
new file mode 100644
index 0000000000..f44f8f8b1f
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/utils/commandParser.test.ts
@@ -0,0 +1,85 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import * as assert from 'assert'
+import { parseBaseCommands } from './commandParser'
+import { split } from 'shlex'
+
+describe('commandParser', () => {
+ describe('parseBaseCommands', () => {
+ it('should extract base command from a simple command', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('cd /home/user/documents')), ['cd'])
+ })
+
+ it('should extract multiple commands separated by &&', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('echo "Hello World" && ls -la')), ['echo', 'ls'])
+ })
+
+ it('should extract multiple commands separated by ||', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('grep "pattern" file.txt || echo "Not found"')), [
+ 'grep',
+ 'echo',
+ ])
+ })
+
+ it('should extract multiple commands separated by |', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('cat file.txt | grep "pattern"')), ['cat', 'grep'])
+ })
+
+ it('should handle commands with quotes', () => {
+ assert.deepStrictEqual(
+ parseBaseCommands(split('echo "text with spaces" && grep "pattern with spaces" file.txt')),
+ ['echo', 'grep']
+ )
+ })
+
+ it('should return empty array for null, undefined, empty input', () => {
+ assert.deepStrictEqual(parseBaseCommands(null as any), [])
+ assert.deepStrictEqual(parseBaseCommands(undefined as any), [])
+ assert.deepStrictEqual(parseBaseCommands(split('')), [])
+ })
+
+ it('should handle commands with semicolons', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('cd /tmp; ls -la; echo "done"')), ['cd', 'ls', 'echo'])
+ })
+
+ it('should handle commands with sudo prefix', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('sudo apt-get install package')), ['sudo', 'apt-get'])
+ assert.deepStrictEqual(parseBaseCommands(split('sudo -u user command')), ['sudo', 'command'])
+ })
+
+ it('should handle commands with time prefix', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('time curl http://example.com')), ['time', 'curl'])
+ })
+
+ it('should handle commands with path prefixes', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('/usr/bin/python script.py')), ['python'])
+ assert.deepStrictEqual(parseBaseCommands(split('./script.sh -arg')), ['script.sh'])
+ assert.deepStrictEqual(parseBaseCommands(split('../bin/tool --option')), ['tool'])
+ })
+
+ it('should handle commands with sudo and path prefixes', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('sudo /usr/bin/apt-get update')), ['sudo', 'apt-get'])
+ })
+
+ it('should handle multiple commands with mixed separators', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('cd /tmp; ls -la | grep "file" && echo "found"')), [
+ 'cd',
+ 'ls',
+ 'grep',
+ 'echo',
+ ])
+ })
+
+ it('should handle commands with other common prefixes', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('nice -n 10 command')), ['nice', 'command'])
+ assert.deepStrictEqual(parseBaseCommands(split('nohup command &')), ['nohup', 'command'])
+ })
+
+ it('should handle commands with function calls', () => {
+ assert.deepStrictEqual(parseBaseCommands(split('function_name args')), ['function_name'])
+ })
+ })
+})
diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/utils/commandParser.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/utils/commandParser.ts
new file mode 100644
index 0000000000..65bdbd2c71
--- /dev/null
+++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/utils/commandParser.ts
@@ -0,0 +1,98 @@
+import { splitOperators } from '../tools/executeBash'
+
+/**
+ * Parses command arguments and extracts only the base commands without arguments or options.
+ *
+ * Examples:
+ * - "cd /home/user/documents" -> ["cd"]
+ * - "echo 'Hello World' && ls -la" -> ["echo", "ls"]
+ * - "sudo apt-get install" -> ["sudo", "apt-get"]
+ * - "time curl http://example.com" -> ["time", "curl"]
+ * - "command1; command2" -> ["command1", "command2"]
+ * - "/usr/bin/python script.py" -> ["python"]
+ * - "./script.sh" -> ["script.sh"]
+ * - "function_name args" -> ["function_name"]
+ *
+ * @param args Array of command arguments
+ * @returns Array of base commands found in the input args
+ */
+export function parseBaseCommands(args: string[]): string[] {
+ if (!args || !Array.isArray(args) || args.length === 0) {
+ return []
+ }
+
+ const baseCommands: string[] = []
+
+ // Process the args to extract base commands
+ let i = 0
+ let expectCommand = true // Flag to indicate we're expecting a command
+
+ while (i < args.length) {
+ const arg = args[i]
+
+ // Check if this arg is an operator or contains an operator
+ if (splitOperators.has(arg) || arg.includes(';')) {
+ expectCommand = true // Next argument should be a command
+ i++
+ continue
+ }
+
+ if (expectCommand) {
+ // Extract the base command
+ let baseCommand = arg
+
+ // Handle path prefixes (/usr/bin/command or ./command)
+ if (baseCommand.includes('/')) {
+ baseCommand = baseCommand.split('/').pop() || baseCommand
+ }
+
+ baseCommands.push(baseCommand)
+
+ // Special case for sudo, time, etc. - include the actual command too
+ if (['sudo', 'time', 'nice', 'nohup', 'env'].includes(baseCommand)) {
+ // Skip any flags/options and their values
+ let j = i + 1
+ while (j < args.length) {
+ // If we find an operator, stop looking for the command
+ if (splitOperators.has(args[j]) || args[j].includes(';')) {
+ break
+ }
+
+ // Skip flag and its value if present
+ if (args[j].startsWith('-')) {
+ // Handle flags with values (e.g., -u user, -n 10)
+ if (
+ j + 1 < args.length &&
+ !args[j + 1].startsWith('-') &&
+ !splitOperators.has(args[j + 1]) &&
+ !args[j + 1].includes(';')
+ ) {
+ j += 2 // Skip both the flag and its value
+ } else {
+ j++ // Skip just the flag
+ }
+ continue
+ }
+
+ // Found the actual command
+ let nextCommand = args[j]
+
+ // Handle path prefixes for the command after sudo/time
+ if (nextCommand.includes('/')) {
+ nextCommand = nextCommand.split('/').pop() || nextCommand
+ }
+
+ baseCommands.push(nextCommand)
+ break
+ }
+ }
+
+ // For all commands, we don't expect another command until we see an operator
+ expectCommand = false
+ }
+
+ i++
+ }
+
+ return baseCommands
+}
diff --git a/server/aws-lsp-codewhisperer/src/language-server/chat/chatSessionService.ts b/server/aws-lsp-codewhisperer/src/language-server/chat/chatSessionService.ts
index 0282a04fcc..92c7eb2c33 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/chat/chatSessionService.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/chat/chatSessionService.ts
@@ -27,7 +27,7 @@ import { AmazonQBaseServiceManager } from '../../shared/amazonQServiceManager/Ba
import { loggingUtils } from '@aws/lsp-core'
import { Logging } from '@aws/language-server-runtimes/server-interface'
import { Features } from '../types'
-import { getOriginFromClientInfo, getRequestID, isUsageLimitError } from '../../shared/utils'
+import { getOriginFromClientInfo, getClientName, getRequestID, isUsageLimitError } from '../../shared/utils'
import { enabledModelSelection } from '../../shared/utils'
export type ChatSessionServiceConfig = CodeWhispererStreamingClientConfig
@@ -138,7 +138,7 @@ export class ChatSessionService {
this.#serviceManager = serviceManager
this.#lsp = lsp
this.#logging = logging
- this.#origin = getOriginFromClientInfo(this.#lsp?.getClientInitializeParams()?.clientInfo?.name)
+ this.#origin = getOriginFromClientInfo(getClientName(this.#lsp?.getClientInitializeParams()))
}
public async sendMessage(request: SendMessageCommandInput): Promise {
diff --git a/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts b/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts
index a2a4de7447..5f96d526b1 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/configuration/qConfigurationServer.ts
@@ -42,6 +42,7 @@ export interface QClientCapabilities {
modelSelection?: boolean
reroute?: boolean
codeReviewInChat?: boolean
+ displayFindings?: boolean
compaction?: boolean
shortcut?: boolean
}
diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts
index 7259aa6a8c..1b4d97728d 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts
@@ -338,7 +338,8 @@ export const CodewhispererServerFactory =
workspace,
logging,
token,
- amazonQServiceManager
+ amazonQServiceManager,
+ params.openTabFilepaths
)
: Promise.resolve(undefined)
@@ -680,6 +681,7 @@ export const CodewhispererServerFactory =
partialResultToken: suggestionResponse.responseContext.nextToken,
}
} else {
+ session.hasEditsPending = suggestionResponse.responseContext.nextToken ? true : false
return {
items: suggestionResponse.suggestions
.map(suggestion => {
@@ -866,7 +868,13 @@ export const CodewhispererServerFactory =
if (firstCompletionDisplayLatency) emitPerceivedLatencyTelemetry(telemetry, session)
// Always emit user trigger decision at session close
- sessionManager.closeSession(session)
+ // Close session unless Edit suggestion was accepted with more pending
+ const shouldKeepSessionOpen =
+ session.suggestionType === SuggestionType.EDIT && isAccepted && session.hasEditsPending
+
+ if (!shouldKeepSessionOpen) {
+ sessionManager.closeSession(session)
+ }
const streakLength = editsEnabled ? sessionManager.getAndUpdateStreakLength(isAccepted) : 0
await emitUserTriggerDecisionTelemetry(
telemetry,
diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/session/sessionManager.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/session/sessionManager.ts
index 35568fc6e2..26a9b3344a 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/session/sessionManager.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/session/sessionManager.ts
@@ -6,7 +6,12 @@ import {
} from '@aws/language-server-runtimes/server-interface'
import { v4 as uuidv4 } from 'uuid'
import { CodewhispererAutomatedTriggerType, CodewhispererTriggerType } from '../auto-trigger/autoTrigger'
-import { GenerateSuggestionsRequest, ResponseContext, Suggestion } from '../../../shared/codeWhispererService'
+import {
+ GenerateSuggestionsRequest,
+ ResponseContext,
+ Suggestion,
+ SuggestionType,
+} from '../../../shared/codeWhispererService'
import { CodewhispererLanguage } from '../../../shared/languageDetection'
import { CodeWhispererSupplementalContext } from '../../../shared/models/model'
import { Logging } from '@aws/language-server-runtimes/server-interface'
@@ -75,6 +80,9 @@ export class CodeWhispererSession {
includeImportsWithSuggestions?: boolean
codewhispererSuggestionImportCount: number = 0
suggestionType?: string
+ hasEditsPending?: boolean = false
+ // Track the most recent itemId for paginated Edit suggestions
+ recentItemId?: string
constructor(data: SessionData) {
this.id = this.generateSessionId()
@@ -153,7 +161,11 @@ export class CodeWhispererSession {
typeaheadLength?: number
) {
// Skip if session results were already recorded for session of session is closed
- if (this.state === 'CLOSED' || this.state === 'DISCARD' || this.completionSessionResult) {
+ if (
+ this.state === 'CLOSED' ||
+ this.state === 'DISCARD' ||
+ (this.completionSessionResult && this.suggestionType === SuggestionType.COMPLETION)
+ ) {
return
}
@@ -195,6 +207,8 @@ export class CodeWhispererSession {
// No recommendation was accepted, but user have seen this suggestion
this.setSuggestionState(itemId, 'Reject')
}
+
+ this.recentItemId = itemId
}
this.firstCompletionDisplayLatency = firstCompletionDisplayLatency
@@ -240,6 +254,24 @@ export class CodeWhispererSession {
}
return isEmpty ? 'Empty' : 'Discard'
}
+
+ /**
+ * Determines trigger decision based on the most recent user action.
+ * Uses the last processed itemId to determine the overall session decision.
+ */
+ getLatestUserTriggerDecision(): UserTriggerDecision | undefined {
+ // Force Discard trigger decision when session was explicitly discarded by server
+ if (this.state === 'DISCARD') {
+ return 'Discard'
+ }
+
+ if (!this.recentItemId) return
+
+ const state = this.getSuggestionState(this.recentItemId)
+ if (state === 'Accept') return 'Accept'
+ if (state === 'Reject') return 'Reject'
+ return state === 'Empty' ? 'Empty' : 'Discard'
+ }
}
export class SessionManager {
diff --git a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/telemetry.ts b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/telemetry.ts
index 4c55ee5b2e..2abb35f801 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/inline-completion/telemetry.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/inline-completion/telemetry.ts
@@ -4,6 +4,7 @@ import { CodeWhispererSession } from './session/sessionManager'
import { CodeWhispererPerceivedLatencyEvent, CodeWhispererServiceInvocationEvent } from '../../shared/telemetry/types'
import { getCompletionType, isAwsError } from '../../shared/utils'
import { TelemetryService } from '../../shared/telemetry/telemetryService'
+import { SuggestionType } from '../../shared/codeWhispererService'
export const emitServiceInvocationTelemetry = (
telemetry: Telemetry,
@@ -121,8 +122,15 @@ export const emitUserTriggerDecisionTelemetry = async (
return
}
+ // Edits show one suggestion sequentially (with pagination), so use latest itemId state;
+ // Completions show multiple suggestions together, so aggregate all states
+ const userTriggerDecision =
+ session.suggestionType === SuggestionType.EDIT
+ ? session.getLatestUserTriggerDecision()
+ : session.getAggregatedUserTriggerDecision()
+
// Can not emit previous trigger decision if it's not available on the session
- if (!session.getAggregatedUserTriggerDecision()) {
+ if (!userTriggerDecision) {
return
}
@@ -137,7 +145,13 @@ export const emitUserTriggerDecisionTelemetry = async (
streakLength
)
- session.reportedUserDecision = true
+ // Mark telemetry as complete unless Edit suggestion was accepted with more pending
+ const hasPendingEditTelemetry =
+ session.suggestionType === SuggestionType.EDIT && session.acceptedSuggestionId && session.hasEditsPending
+
+ if (!hasPendingEditTelemetry) {
+ session.reportedUserDecision = true
+ }
}
export const emitAggregatedUserTriggerDecisionTelemetry = (
diff --git a/server/aws-lsp-codewhisperer/src/language-server/workspaceContext/artifactManager.ts b/server/aws-lsp-codewhisperer/src/language-server/workspaceContext/artifactManager.ts
index e2ad2db788..9518f9629a 100644
--- a/server/aws-lsp-codewhisperer/src/language-server/workspaceContext/artifactManager.ts
+++ b/server/aws-lsp-codewhisperer/src/language-server/workspaceContext/artifactManager.ts
@@ -66,6 +66,7 @@ interface FileSizeDetails {
skippedSize: number
}
const MAX_UNCOMPRESSED_SRC_SIZE_BYTES = 2 * 1024 * 1024 * 1024 // 2 GB
+const MAX_FILES = 500_000
export class ArtifactManager {
private workspace: Workspace
@@ -175,7 +176,8 @@ export class ArtifactManager {
}
if (isDirectory(filePath)) {
- const files = await glob(['**/*'], {
+ let fileCount = 0
+ const filesStream = glob.stream(['**/*'], {
cwd: filePath,
dot: false,
ignore: IGNORE_DEPENDENCY_PATTERNS,
@@ -184,7 +186,11 @@ export class ArtifactManager {
onlyFiles: true,
})
- for (const relativePath of files) {
+ for await (const entry of filesStream) {
+ if (fileCount >= MAX_FILES) {
+ break
+ }
+ const relativePath = entry.toString()
try {
const fullPath = resolveSymlink(path.join(filePath, relativePath))
const fileMetadata = await this.createFileMetadata(
@@ -197,6 +203,7 @@ export class ArtifactManager {
} catch (error) {
this.logging.warn(`Error processing file ${relativePath}: ${error}`)
}
+ fileCount++
}
} else {
const workspaceUri = URI.parse(currentWorkspace.uri)
@@ -359,7 +366,8 @@ export class ArtifactManager {
): Promise