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 [![codecov](https://codecov.io/github/aws/language-servers/graph/badge.svg?token=ZSHpIVkG8S)](https://codecov.io/github/aws/language-servers) +[![Integration Tests](https://github.com/aws/language-servers/actions/workflows/integration-tests.yml/badge.svg)](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> { const filesByLanguage = new Map() - const files = await glob(['**/*'], { + const files = [] + const filesStream = glob.stream(['**/*'], { cwd: directoryPath, dot: false, ignore: IGNORE_PATTERNS, @@ -368,11 +376,26 @@ export class ArtifactManager { onlyFiles: true, }) + for await (const entry of filesStream) { + if (files.length >= MAX_FILES) { + break + } + files.push(entry.toString()) + } + + const hasJavaFile = files.some(file => file.endsWith('.java')) + for (const relativePath of files) { const fullPath = path.join(directoryPath, relativePath) - const language = getCodeWhispererLanguageIdFromPath(fullPath) - - if (!language || !SUPPORTED_WORKSPACE_CONTEXT_LANGUAGES.includes(language)) { + const isJavaProjectFile = isJavaProjectFileFromPath(fullPath) + const language = isJavaProjectFileFromPath(fullPath) ? 'java' : getCodeWhispererLanguageIdFromPath(fullPath) + + if ( + !language || + !SUPPORTED_WORKSPACE_CONTEXT_LANGUAGES.includes(language) || + // skip processing the java project file if there's no java source file + (!hasJavaFile && isJavaProjectFile) + ) { continue } @@ -631,7 +654,7 @@ export class ArtifactManager { files: FileMetadata[] ): Promise { const workspacePath = URI.parse(workspaceFolder.uri).path - const hasJavaFiles = files.some(file => file.language === 'java' && file.relativePath.endsWith('java')) + const hasJavaFiles = files.some(file => file.language === 'java' && file.relativePath.endsWith('.java')) if (!hasJavaFiles) { return files diff --git a/server/aws-lsp-codewhisperer/src/shared/constants.ts b/server/aws-lsp-codewhisperer/src/shared/constants.ts index 968f6691ca..cd453a11ba 100644 --- a/server/aws-lsp-codewhisperer/src/shared/constants.ts +++ b/server/aws-lsp-codewhisperer/src/shared/constants.ts @@ -18,6 +18,8 @@ export const AWS_Q_ENDPOINT_URL_ENV_VAR = 'AWS_Q_ENDPOINT_URL' export const Q_CONFIGURATION_SECTION = 'aws.q' export const CODE_WHISPERER_CONFIGURATION_SECTION = 'aws.codeWhisperer' +export const SAGEMAKER_UNIFIED_STUDIO_SERVICE = 'SageMakerUnifiedStudio' + /** * Names of directories relevant to the crash reporting functionality. * diff --git a/server/aws-lsp-codewhisperer/src/shared/languageDetection.test.ts b/server/aws-lsp-codewhisperer/src/shared/languageDetection.test.ts index 67873bee0c..c8a1193677 100644 --- a/server/aws-lsp-codewhisperer/src/shared/languageDetection.test.ts +++ b/server/aws-lsp-codewhisperer/src/shared/languageDetection.test.ts @@ -45,10 +45,6 @@ describe('LanguageDetection', () => { describe('getCodeWhispererLanguageIdFromPath', () => { it('should return language type with override', () => { - assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/pom.xml'), 'java') - assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/build.gradle.kts'), 'java') - assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/build.xml'), 'java') - assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/build.gradle'), 'java') assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/test.java'), 'java') assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/package.json'), 'javascript') assert.strictEqual(getCodeWhispererLanguageIdFromPath('test/test.js'), 'javascript') diff --git a/server/aws-lsp-codewhisperer/src/shared/languageDetection.ts b/server/aws-lsp-codewhisperer/src/shared/languageDetection.ts index d8a4074d92..ab3761528b 100644 --- a/server/aws-lsp-codewhisperer/src/shared/languageDetection.ts +++ b/server/aws-lsp-codewhisperer/src/shared/languageDetection.ts @@ -294,8 +294,6 @@ export function getCodeWhispererLanguageIdFromPath(filePath: string): Codewhispe return 'javascript' } - if (isJavaProjectFileFromPath(filePath)) return 'java' - for (const [extension, languageId] of Object.entries(languageByExtension)) { if (filePath.endsWith(extension)) { return getRuntimeLanguage(languageId) diff --git a/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/crossFileContextUtil.ts b/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/crossFileContextUtil.ts index f29742a35d..d1de00c541 100644 --- a/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/crossFileContextUtil.ts +++ b/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/crossFileContextUtil.ts @@ -67,7 +67,8 @@ export async function fetchSupplementalContextForSrc( position: Position, workspace: Workspace, cancellationToken: CancellationToken, - amazonQServiceManager?: AmazonQBaseServiceManager + amazonQServiceManager?: AmazonQBaseServiceManager, + openTabFiles?: string[] ): Promise | undefined> { const supplementalContextConfig = getSupplementalContextConfig(document.languageId) @@ -76,7 +77,14 @@ export async function fetchSupplementalContextForSrc( } //TODO: add logic for other strategies once available if (supplementalContextConfig === 'codemap') { - return await codemapContext(document, position, workspace, cancellationToken, amazonQServiceManager) + return await codemapContext( + document, + position, + workspace, + cancellationToken, + amazonQServiceManager, + openTabFiles + ) } return { supplementalContextItems: [], strategy: 'Empty' } } @@ -86,13 +94,14 @@ export async function codemapContext( position: Position, workspace: Workspace, cancellationToken: CancellationToken, - amazonQServiceManager?: AmazonQBaseServiceManager + amazonQServiceManager?: AmazonQBaseServiceManager, + openTabFiles?: string[] ): Promise | undefined> { let strategy: SupplementalContextStrategy = 'Empty' const openTabsContextPromise = waitUntil( async function () { - return await fetchOpenTabsContext(document, position, workspace, cancellationToken) + return await fetchOpenTabsContext(document, position, workspace, cancellationToken, openTabFiles) }, { timeout: supplementalContextTimeoutInMs, interval: 5, truthy: false } ) @@ -160,12 +169,13 @@ export async function fetchOpenTabsContext( document: TextDocument, position: Position, workspace: Workspace, - cancellationToken: CancellationToken + cancellationToken: CancellationToken, + openTabFiles?: string[] ): Promise { const codeChunksCalculated = crossFileContextConfig.numberOfChunkToFetch // Step 1: Get relevant cross files to refer - const relevantCrossFileCandidates = await getCrossFileCandidates(document, workspace) + const relevantCrossFileCandidates = await getCrossFileCandidates(document, workspace, openTabFiles) throwIfCancelled(cancellationToken) @@ -318,11 +328,102 @@ function createFileUrl(uri: string): URL { return nodeUrl.pathToFileURL(resolvedPath) } +/** + * Returns the language id for construction a TextDocument + * @param filepath + * @returns + */ + +function guessLanguageId(filepath: string): string { + const ext = path.extname(filepath).toLowerCase() + switch (ext) { + case '.abap': + return 'abap' + case '.c': + return 'c' + case '.cpp': + case '.cc': + case '.cxx': + case '.hpp': + case '.h': + return 'cpp' + case '.cs': + return 'csharp' + case '.dart': + return 'dart' + case '.go': + return 'go' + case '.java': + return 'java' + case '.js': + return 'javascript' + case '.json': + return 'json' + case '.jsx': + return 'jsx' + case '.kt': + case '.kts': + return 'kotlin' + case '.lua': + return 'lua' + case '.php': + return 'php' + case '.txt': + return 'plaintext' + case '.ps1': + case '.psm1': + case '.psd1': + return 'powershell' + case '.py': + return 'python' + case '.r': + case '.R': + return 'r' + case '.rb': + case '.rbw': + return 'ruby' + case '.rs': + return 'rust' + case '.scala': + case '.sc': + return 'scala' + case '.sh': + case '.bash': + return 'shell' + case '.sql': + return 'sql' + case '.swift': + return 'swift' + case '.sv': + case '.svh': + case '.v': + return 'systemverilog' + case '.tf': + case '.tfvars': + return 'tf' + case '.tsx': + return 'tsx' + case '.ts': + return 'typescript' + case '.vue': + return 'vue' + case '.yml': + case '.yaml': + return 'yaml' + default: + return '' + } +} + /** * This function will return relevant cross files sorted by file distance for the given editor file * by referencing open files, imported files and same package files. */ -export async function getCrossFileCandidates(document: TextDocument, workspace: Workspace): Promise { +export async function getCrossFileCandidates( + document: TextDocument, + workspace: Workspace, + openTabFiles?: string[] +): Promise { const targetFile = document.uri const language = document.languageId as CrossFileSupportedLanguage const dialects = supportedLanguageToDialects[language] @@ -335,8 +436,34 @@ export async function getCrossFileCandidates(document: TextDocument, workspace: * * Porting note: this function relies of Workspace feature to get all documents, * managed by this language server, instead of VSCode `vscode.window` API as VSCode toolkit does. + * this function only gets the user opened tab in this IDE session + * for a resumed IDE session, opened tabs are restored but this getAllTextDocuments function returns empty + * in that case we manually create TextDocuments from it */ - const unsortedCandidates = await workspace.getAllTextDocuments() + let unsortedCandidates: TextDocument[] = await workspace.getAllTextDocuments() + if (openTabFiles && openTabFiles.length > 0) { + for (const openTabFile of openTabFiles) { + try { + const openTabFilesUri = URI.file(openTabFile) + if (!unsortedCandidates.some(x => x.uri === openTabFilesUri.toString())) { + const content = await workspace.fs.readFile(openTabFilesUri.fsPath) + if (content) { + unsortedCandidates.push( + TextDocument.create( + URI.file(openTabFile).toString(), + guessLanguageId(openTabFilesUri.fsPath), + 1, + content + ) + ) + } + } + } catch (e) { + // do not throw here. + } + } + } + return unsortedCandidates .filter((candidateFile: TextDocument) => { let candidateFileURL diff --git a/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/supplementalContextUtil.ts b/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/supplementalContextUtil.ts index 8d17e44057..c95aeea1d2 100644 --- a/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/supplementalContextUtil.ts +++ b/server/aws-lsp-codewhisperer/src/shared/supplementalContextUtil/supplementalContextUtil.ts @@ -29,7 +29,8 @@ export async function fetchSupplementalContext( workspace: Workspace, logging: Logging, cancellationToken: CancellationToken, - amazonQServiceManager?: AmazonQBaseServiceManager + amazonQServiceManager?: AmazonQBaseServiceManager, + openTabFiles?: string[] ): Promise { const timesBeforeFetching = performance.now() @@ -73,7 +74,8 @@ export async function fetchSupplementalContext( position, workspace, cancellationToken, - amazonQServiceManager + amazonQServiceManager, + openTabFiles ) } diff --git a/server/aws-lsp-codewhisperer/src/shared/telemetry/telemetryService.ts b/server/aws-lsp-codewhisperer/src/shared/telemetry/telemetryService.ts index 5f7a806427..bdb88ef756 100644 --- a/server/aws-lsp-codewhisperer/src/shared/telemetry/telemetryService.ts +++ b/server/aws-lsp-codewhisperer/src/shared/telemetry/telemetryService.ts @@ -117,7 +117,13 @@ export class TelemetryService { private getSuggestionState(session: CodeWhispererSession): SuggestionState { let suggestionState: SuggestionState - switch (session.getAggregatedUserTriggerDecision()) { + // 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() + switch (userTriggerDecision) { case 'Accept': suggestionState = 'ACCEPT' break diff --git a/server/aws-lsp-codewhisperer/src/shared/telemetry/types.ts b/server/aws-lsp-codewhisperer/src/shared/telemetry/types.ts index f0a28cd340..dc6f7705fb 100644 --- a/server/aws-lsp-codewhisperer/src/shared/telemetry/types.ts +++ b/server/aws-lsp-codewhisperer/src/shared/telemetry/types.ts @@ -210,6 +210,7 @@ export enum ChatTelemetryEventName { ExportTab = 'amazonq_exportTab', UiClick = 'ui_click', ActiveUser = 'amazonq_activeUser', + BashCommand = 'amazonq_bashCommand', } export interface ChatTelemetryEventMap { @@ -234,6 +235,7 @@ export interface ChatTelemetryEventMap { [ChatTelemetryEventName.ExportTab]: ExportTabEvent [ChatTelemetryEventName.UiClick]: UiClickEvent [ChatTelemetryEventName.ActiveUser]: ActiveUserEvent + [ChatTelemetryEventName.BashCommand]: BashCommandEvent } export type AgencticLoop_InvokeLLMEvent = { @@ -271,6 +273,12 @@ export type ActiveUserEvent = { result: string } +export type BashCommandEvent = { + credentialStartUrl: string + result: string + command: string +} + export type ModifyCodeEvent = { cwsprChatConversationId: string cwsprChatMessageId: string diff --git a/server/aws-lsp-codewhisperer/src/shared/utils.test.ts b/server/aws-lsp-codewhisperer/src/shared/utils.test.ts index 5d0062bac2..e83d04bbb3 100644 --- a/server/aws-lsp-codewhisperer/src/shared/utils.test.ts +++ b/server/aws-lsp-codewhisperer/src/shared/utils.test.ts @@ -3,14 +3,14 @@ import { ThrottlingException, ThrottlingExceptionReason, } from '@amzn/codewhisperer-streaming' -import { CredentialsProvider, Position } from '@aws/language-server-runtimes/server-interface' +import { CredentialsProvider, Position, InitializeParams } from '@aws/language-server-runtimes/server-interface' import * as assert from 'assert' import { AWSError } from 'aws-sdk' import { expect } from 'chai' import * as sinon from 'sinon' import * as os from 'os' import * as path from 'path' -import { BUILDER_ID_START_URL } from './constants' +import { BUILDER_ID_START_URL, SAGEMAKER_UNIFIED_STUDIO_SERVICE } from './constants' import { getBearerTokenFromProvider, getEndPositionForAcceptedSuggestion, @@ -24,6 +24,7 @@ import { getFileExtensionName, listFilesWithGitignore, getOriginFromClientInfo, + getClientName, sanitizeInput, sanitizeRequestInput, } from './utils' @@ -73,12 +74,86 @@ describe('getBearerTokenFromProvider', () => { }) }) +describe('getClientName', () => { + let originalEnv: string | undefined + + beforeEach(() => { + originalEnv = process.env.SERVICE_NAME + }) + + afterEach(() => { + if (originalEnv !== undefined) { + process.env.SERVICE_NAME = originalEnv + } else { + delete process.env.SERVICE_NAME + } + }) + + it('returns client name from initializationOptions path when SERVICE_NAME is SageMakerUnifiedStudio', () => { + process.env.SERVICE_NAME = SAGEMAKER_UNIFIED_STUDIO_SERVICE + const lspParams = { + initializationOptions: { + aws: { + clientInfo: { + name: 'AmazonQ-For-SMUS-CE-1.0.0', + }, + }, + }, + clientInfo: { + name: 'VSCode-Extension', + }, + } as InitializeParams + + const result = getClientName(lspParams) + assert.strictEqual(result, 'AmazonQ-For-SMUS-CE-1.0.0') + }) + + it('returns client name from clientInfo path when SERVICE_NAME is not SageMakerUnifiedStudio', () => { + process.env.SERVICE_NAME = 'SomeOtherService' + const lspParams = { + initializationOptions: { + aws: { + clientInfo: { + name: 'AmazonQ-For-SMUS-CE-1.0.0', + }, + }, + }, + clientInfo: { + name: 'VSCode-Extension', + }, + } as InitializeParams + + const result = getClientName(lspParams) + assert.strictEqual(result, 'VSCode-Extension') + }) + + it('returns undefined when lspParams is undefined', () => { + const result = getClientName(undefined) + assert.strictEqual(result, undefined) + }) +}) + describe('getOriginFromClientInfo', () => { - it('returns MD_IDE for SMUS client name', () => { + it('returns MD_IDE for SMUS-IDE client name', () => { const result = getOriginFromClientInfo('AmazonQ-For-SMUS-IDE-1.0.0') assert.strictEqual(result, 'MD_IDE') }) + it('returns MD_IDE for SMUS-CE client name', () => { + const result = getOriginFromClientInfo('AmazonQ-For-SMUS-CE-1.0.0') + assert.strictEqual(result, 'MD_IDE') + }) + + it('returns MD_IDE for client names starting with SMUS-IDE prefix', () => { + const result = getOriginFromClientInfo('AmazonQ-For-SMUS-IDE') + assert.strictEqual(result, 'MD_IDE') + }) + + it('returns MD_IDE for client names starting with SMUS-CE prefix', () => { + const result = getOriginFromClientInfo('AmazonQ-For-SMUS-CE') + assert.strictEqual(result, 'MD_IDE') + }) + it('returns IDE for non-SMUS client name', () => { const result = getOriginFromClientInfo('VSCode-Extension') assert.strictEqual(result, 'IDE') @@ -93,6 +168,11 @@ describe('getOriginFromClientInfo', () => { const result = getOriginFromClientInfo('') assert.strictEqual(result, 'IDE') }) + + it('returns IDE for client names that do not match SMUS patterns', () => { + const result = getOriginFromClientInfo('AmazonQ-For-Other-IDE') + assert.strictEqual(result, 'IDE') + }) }) describe('getSsoConnectionType', () => { diff --git a/server/aws-lsp-codewhisperer/src/shared/utils.ts b/server/aws-lsp-codewhisperer/src/shared/utils.ts index b896540f72..e178de4a51 100644 --- a/server/aws-lsp-codewhisperer/src/shared/utils.ts +++ b/server/aws-lsp-codewhisperer/src/shared/utils.ts @@ -14,6 +14,7 @@ import { crashMonitoringDirName, driveLetterRegex, MISSING_BEARER_TOKEN_ERROR, + SAGEMAKER_UNIFIED_STUDIO_SERVICE, } from './constants' import { CodeWhispererStreamingServiceException, @@ -373,8 +374,14 @@ export function getBearerTokenFromProvider(credentialsProvider: CredentialsProvi return credentials.token } +export function getClientName(lspParams: InitializeParams | undefined): string | undefined { + return process.env.SERVICE_NAME === SAGEMAKER_UNIFIED_STUDIO_SERVICE + ? lspParams?.initializationOptions?.aws?.clientInfo?.name + : lspParams?.clientInfo?.name +} + export function getOriginFromClientInfo(clientName: string | undefined): Origin { - if (clientName?.startsWith('AmazonQ-For-SMUS-IDE')) { + if (clientName?.startsWith('AmazonQ-For-SMUS-IDE') || clientName?.startsWith('AmazonQ-For-SMUS-CE')) { return 'MD_IDE' } return 'IDE' diff --git a/server/aws-lsp-identity/package.json b/server/aws-lsp-identity/package.json index 4ba1330a98..929bee5583 100644 --- a/server/aws-lsp-identity/package.json +++ b/server/aws-lsp-identity/package.json @@ -26,7 +26,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", diff --git a/server/aws-lsp-json/CHANGELOG.md b/server/aws-lsp-json/CHANGELOG.md index c16adea0ff..31c977b978 100644 --- a/server/aws-lsp-json/CHANGELOG.md +++ b/server/aws-lsp-json/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.1.17](https://github.com/aws/language-servers/compare/lsp-json/v0.1.16...lsp-json/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-json/v0.1.15...lsp-json/v0.1.16) (2025-07-17) diff --git a/server/aws-lsp-json/package.json b/server/aws-lsp-json/package.json index f9e890d6fe..df4d04e39a 100644 --- a/server/aws-lsp-json/package.json +++ b/server/aws-lsp-json/package.json @@ -1,6 +1,6 @@ { "name": "@aws/lsp-json", - "version": "0.1.16", + "version": "0.1.17", "description": "JSON Language Server", "main": "out/index.js", "repository": { @@ -26,8 +26,8 @@ "prepack": "shx cp ../../LICENSE ../../NOTICE ../../SECURITY.md ." }, "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" }, diff --git a/server/aws-lsp-notification/package.json b/server/aws-lsp-notification/package.json index 0dadf10062..6cc3037232 100644 --- a/server/aws-lsp-notification/package.json +++ b/server/aws-lsp-notification/package.json @@ -22,7 +22,7 @@ "coverage:report": "c8 report --reporter=html --reporter=text" }, "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" }, diff --git a/server/aws-lsp-partiql/CHANGELOG.md b/server/aws-lsp-partiql/CHANGELOG.md index 8b9af69338..b1be4c4050 100644 --- a/server/aws-lsp-partiql/CHANGELOG.md +++ b/server/aws-lsp-partiql/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.0.16](https://github.com/aws/language-servers/compare/lsp-partiql/v0.0.15...lsp-partiql/v0.0.16) (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.15](https://github.com/aws/language-servers/compare/lsp-partiql/v0.0.14...lsp-partiql/v0.0.15) (2025-07-17) diff --git a/server/aws-lsp-partiql/package.json b/server/aws-lsp-partiql/package.json index 5dcfc97664..620b3ba16b 100644 --- a/server/aws-lsp-partiql/package.json +++ b/server/aws-lsp-partiql/package.json @@ -3,7 +3,7 @@ "author": "Amazon Web Services", "license": "Apache-2.0", "description": "PartiQL language server", - "version": "0.0.15", + "version": "0.0.16", "repository": { "type": "git", "url": "https://github.com/aws/language-servers" @@ -24,7 +24,7 @@ "out" ], "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" diff --git a/server/aws-lsp-s3/package.json b/server/aws-lsp-s3/package.json index 5183be5c64..e971b6af08 100644 --- a/server/aws-lsp-s3/package.json +++ b/server/aws-lsp-s3/package.json @@ -9,7 +9,7 @@ "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" diff --git a/server/aws-lsp-yaml/CHANGELOG.md b/server/aws-lsp-yaml/CHANGELOG.md index 8e58c69c67..965da41cfc 100644 --- a/server/aws-lsp-yaml/CHANGELOG.md +++ b/server/aws-lsp-yaml/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [0.1.17](https://github.com/aws/language-servers/compare/lsp-yaml/v0.1.16...lsp-yaml/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-yaml/v0.1.15...lsp-yaml/v0.1.16) (2025-07-17) diff --git a/server/aws-lsp-yaml/package.json b/server/aws-lsp-yaml/package.json index c7c2939613..2231bd884a 100644 --- a/server/aws-lsp-yaml/package.json +++ b/server/aws-lsp-yaml/package.json @@ -1,6 +1,6 @@ { "name": "@aws/lsp-yaml", - "version": "0.1.16", + "version": "0.1.17", "description": "YAML Language Server", "main": "out/index.js", "repository": { @@ -26,8 +26,8 @@ "postinstall": "node patchYamlPackage.js" }, "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" diff --git a/server/device-sso-auth-lsp/package.json b/server/device-sso-auth-lsp/package.json index 0cf7c53dec..529b59ad6b 100644 --- a/server/device-sso-auth-lsp/package.json +++ b/server/device-sso-auth-lsp/package.json @@ -7,7 +7,7 @@ "compile": "tsc --build" }, "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/hello-world-lsp/package.json b/server/hello-world-lsp/package.json index 4f2fed3858..e24f1d0ff7 100644 --- a/server/hello-world-lsp/package.json +++ b/server/hello-world-lsp/package.json @@ -13,7 +13,7 @@ "coverage:report": "c8 report --reporter=html --reporter=text" }, "dependencies": { - "@aws/language-server-runtimes": "^0.2.120", + "@aws/language-server-runtimes": "^0.2.121", "vscode-languageserver": "^9.0.1" }, "devDependencies": {