diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c32f432ddac..68e055e6886 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,4 @@ * @aws/aws-ides-team packages/core/src/codewhisperer/ @aws/codewhisperer-team packages/core/src/amazonqFeatureDev/ @aws/earlybird -packages/core/src/codewhispererChat/ @aws/flare -packages/core/src/amazonq/ @aws/flare packages/core/src/awsService/accessanalyzer/ @aws/access-analyzer diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fa18d38dc81..6c9bcba59f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -534,6 +534,7 @@ Unlike the user setting overrides, not all of these environment variables have t - `AWS_TOOLKIT_TEST_NO_COLOR`: If the tests should include colour in their output - `DEVELOPMENT_PATH`: The path to the aws toolkit vscode project - `TEST_DIR` - The directory where the test runner should find the tests +- `AMAZONQ_FEATUREDEV_ITERATION_TEST` - Controls whether to enable multiple iteration testing for Amazon Q feature development ### SAM/CFN ("goformation") JSON schema diff --git a/docs/lsp.md b/docs/lsp.md index 22ada2175fa..a0c7a25d8cb 100644 --- a/docs/lsp.md +++ b/docs/lsp.md @@ -26,9 +26,7 @@ sequenceDiagram ## Language Server Debugging -1. Clone https://github.com/aws/language-servers.git and set it up in the same workspace as this project - - e.g. +1. Clone https://github.com/aws/language-servers.git and set it up in the same workspace as this project by cmd+shift+p and "add folder to workspace" and selecting the language-servers folder that you just cloned. Your VS code folder structure should look like below. ``` /aws-toolkit-vscode @@ -52,9 +50,9 @@ sequenceDiagram "amazonqLSPChat": true // optional: enables chat from flare } ``` -4. Uncomment the `__AMAZONQLSP_PATH` variable in `amazonq/.vscode/launch.json` Extension configuration - 1. Uncomment the `__AMAZONQLSP_UI` variable in `amazonq/.vscode/launch.json` Extension configuration if you want to debug the flare chat-client as well +4. Uncomment the `__AMAZONQLSP_PATH` and `__AMAZONQLSP_UI` variables in the `amazonq/.vscode/launch.json` extension configuration 5. Use the `Launch LSP with Debugging` configuration and set breakpoints in VSCode or the language server +6. (Optional): Enable `"amazonq.trace.server": "on"` or `"amazonq.trace.server": "verbose"` in your VSCode settings to view detailed log messages sent to/from the language server. These log messages will show up in the "Amazon Q Language Server" output channel ## Amazon Q Inline Activation diff --git a/package-lock.json b/package-lock.json index 35e15539e63..e5f1e7e7a70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "vscode-nls-dev": "^4.0.4" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.312", + "@aws-toolkits/telemetry": "^1.0.318", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", @@ -70,8 +70,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -642,8 +640,6 @@ }, "node_modules/@aws-sdk/client-apprunner": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-apprunner/-/client-apprunner-3.693.0.tgz", - "integrity": "sha512-6q3yxzp+1fZ2+O7NC8skDz7GSRH6fCcRfT9UU1nX3+kIx/C9cbutnM/WxU35vqJrnT4hq45cUoWj52xZgxFgAA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -694,8 +690,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/client-sso": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -743,8 +737,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -796,8 +788,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/client-sts": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -847,8 +837,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/core": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -869,8 +857,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/credential-provider-http": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -890,8 +876,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -916,8 +900,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/credential-provider-node": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.693.0", @@ -939,8 +921,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.693.0", @@ -958,8 +938,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -977,8 +955,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/middleware-host-header": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -992,8 +968,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/middleware-logger": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1006,8 +980,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1021,8 +993,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -1039,8 +1009,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/region-config-resolver": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1056,8 +1024,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/token-providers": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1075,8 +1041,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/util-endpoints": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1090,8 +1054,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -1102,8 +1064,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.693.0", @@ -1126,8 +1086,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -1138,8 +1096,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", @@ -1151,8 +1107,6 @@ }, "node_modules/@aws-sdk/client-apprunner/node_modules/@smithy/util-utf8": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", @@ -10806,11 +10760,10 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.312", - "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.312.tgz", - "integrity": "sha512-Ufr24XeVrkBrsyUZyGRXprclkGsF/5O16IXP0dW7LC2DMqFyMuvmcHhIkQDN9D8ydnsHdutj/ZxTyvpkHpXQJw==", + "version": "1.0.318", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.318.tgz", + "integrity": "sha512-L64GJ+KRN0fdTIx1CPIbbgBeFcg9DilsIxfjeZyod7ld0mw6he70rPopBtK4jP+pTEkfUE4wTRsaco1nWXz3+w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "ajv": "^6.12.6", "cross-spawn": "^7.0.6", @@ -10821,25 +10774,35 @@ "yargs": "^17.0.1" } }, + "node_modules/@aws/chat-client": { + "version": "0.1.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws/chat-client-ui-types": "^0.1.12", + "@aws/language-server-runtimes-types": "^0.1.10", + "@aws/mynah-ui": "^4.28.0" + } + }, "node_modules/@aws/chat-client-ui-types": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@aws/chat-client-ui-types/-/chat-client-ui-types-0.1.12.tgz", - "integrity": "sha512-2yfRwElcJOwJ8dBR8aLcWAy9HlULRluaVCerCHXV+LstiIvkS0cd92l248Y+RQlKAvbGZXpnEzUOrlkNWVa80Q==", + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@aws/chat-client-ui-types/-/chat-client-ui-types-0.1.26.tgz", + "integrity": "sha512-WlF0fP1nojueknr815dg6Ivs+Q3e5onvWTH1nI05jysSzUHjsWwFDBrsxqJXfaPIFhPrbQzHqoxHbhIwQ1OLuw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws/language-server-runtimes-types": "^0.1.10" + "@aws/language-server-runtimes-types": "^0.1.22" } }, "node_modules/@aws/language-server-runtimes": { - "version": "0.2.58", - "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes/-/language-server-runtimes-0.2.58.tgz", - "integrity": "sha512-gb1oLKACFpmDKkzSdDAqMdpo63m+Kul4B/uVNNO1IFN4+wEP7zPVgmd1dLDPlLKHrxsAEQDxoYDaYVyQ+yJKqQ==", + "version": "0.2.70", "dev": true, + "license": "Apache-2.0", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.9.3", "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/client-cognito-identity": "^3.758.0", - "@aws/language-server-runtimes-types": "^0.1.13", + "@aws/language-server-runtimes-types": "^0.1.21", "@opentelemetry/api": "^1.9.0", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-metrics": "^1.30.1", @@ -10853,7 +10816,7 @@ "aws-sdk": "^2.1692.0", "axios": "^1.8.4", "hpagent": "^1.2.0", - "jose": "^6.0.10", + "jose": "^5.9.6", "mac-ca": "^3.1.1", "rxjs": "^7.8.2", "vscode-languageserver": "^9.0.1", @@ -10865,10 +10828,11 @@ } }, "node_modules/@aws/language-server-runtimes-types": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.13.tgz", - "integrity": "sha512-+FJREN6qyNcOwbu0fxAKt0QUh6x10xg2a9fLL722yVisXV0p4ElgHuXssOWhwALJrmy47DF7bRunZYNpFR9mqw==", + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.26.tgz", + "integrity": "sha512-c63rpUbcrtLqaC33t6elRApQqLbQvFgKzIQ2z/VCavE5F7HSLBfzhHkhgUFd775fBpsF4MHrIzwNitYLhDGobw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5" @@ -10876,8 +10840,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/client-cognito-identity": { "version": "3.768.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.768.0.tgz", - "integrity": "sha512-h/WOvKhuXVIhNKjDcsF6oY2oJuBusspnmEaX20h+GUzIrNMlf6qkJrWziT58KzzESyzeYZcGNWjcOfbVRpH6NA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -10927,8 +10889,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/client-sso": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.758.0.tgz", - "integrity": "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -10977,8 +10937,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/core": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.758.0.tgz", - "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11000,8 +10958,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/credential-provider-env": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.758.0.tgz", - "integrity": "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11017,8 +10973,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/credential-provider-http": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.758.0.tgz", - "integrity": "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11039,8 +10993,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/credential-provider-node": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.758.0.tgz", - "integrity": "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11063,8 +11015,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/credential-provider-process": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.758.0.tgz", - "integrity": "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11081,8 +11031,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.758.0.tgz", - "integrity": "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11101,8 +11049,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/middleware-host-header": { "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.734.0.tgz", - "integrity": "sha512-LW7RRgSOHHBzWZnigNsDIzu3AiwtjeI2X66v+Wn1P1u+eXssy1+up4ZY/h+t2sU4LU36UvEf+jrZti9c6vRnFw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11117,8 +11063,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/middleware-logger": { "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.734.0.tgz", - "integrity": "sha512-mUMFITpJUW3LcKvFok176eI5zXAUomVtahb9IQBwLzkqFYOrMJvWAvoV4yuxrJ8TlQBG8gyEnkb9SnhZvjg67w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11132,8 +11076,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.734.0.tgz", - "integrity": "sha512-CUat2d9ITsFc2XsmeiRQO96iWpxSKYFjxvj27Hc7vo87YUHRnfMfnc8jw1EpxEwMcvBD7LsRa6vDNky6AjcrFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11148,8 +11090,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.758.0.tgz", - "integrity": "sha512-iNyehQXtQlj69JCgfaOssgZD4HeYGOwxcaKeG6F+40cwBjTAi0+Ph1yfDwqk2qiBPIRWJ/9l2LodZbxiBqgrwg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11167,8 +11107,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/region-config-resolver": { "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", - "integrity": "sha512-Lvj1kPRC5IuJBr9DyJ9T9/plkh+EfKLy+12s/mykOy1JaKHDpvj+XGy2YO6YgYVOb8JFtaqloid+5COtje4JTQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11185,8 +11123,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/token-providers": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz", - "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11203,8 +11139,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/types": { "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.734.0.tgz", - "integrity": "sha512-o11tSPTT70nAkGV1fN9wm/hAIiLPyWX6SuGf+9JyTp7S/rC2cFWhR26MvA69nplcjNaXVzB0f+QFrLXXjOqCrg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11217,8 +11151,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/util-endpoints": { "version": "3.743.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.743.0.tgz", - "integrity": "sha512-sN1l559zrixeh5x+pttrnd0A3+r34r0tmPkJ/eaaMaAzXqsmKU/xYre9K3FNnsSS1J1k4PEfk/nHDTVUgFYjnw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11233,8 +11165,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.734.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.734.0.tgz", - "integrity": "sha512-xQTCus6Q9LwUuALW+S76OL0jcWtMOVu14q+GoLnWPUM7QeUw963oQcLhF7oq0CtaLLKyl4GOUfcwc773Zmwwng==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11246,8 +11176,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.758.0.tgz", - "integrity": "sha512-A5EZw85V6WhoKMV2hbuFRvb9NPlxEErb4HPO6/SPXYY4QrjprIzScHxikqcWv1w4J3apB1wto9LPU3IMsYtfrw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11271,8 +11199,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/abort-controller": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", - "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11285,8 +11211,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/config-resolver": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", - "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11302,8 +11226,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/core": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.5.tgz", - "integrity": "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11322,8 +11244,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/credential-provider-imds": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", - "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11339,8 +11259,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/fetch-http-handler": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", - "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11356,8 +11274,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/hash-node": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", - "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11372,8 +11288,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/invalid-dependency": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", - "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11386,8 +11300,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11399,8 +11311,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/middleware-content-length": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", - "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11414,8 +11324,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/middleware-endpoint": { "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.6.tgz", - "integrity": "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11434,8 +11342,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/middleware-retry": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.7.tgz", - "integrity": "sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11455,8 +11361,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/middleware-serde": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz", - "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11469,8 +11373,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/middleware-stack": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", - "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11483,8 +11385,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/node-config-provider": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", - "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11499,8 +11399,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/node-http-handler": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.3.tgz", - "integrity": "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11516,8 +11414,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/property-provider": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", - "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11530,8 +11426,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/protocol-http": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", - "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11544,8 +11438,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/querystring-builder": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", - "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11559,8 +11451,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/querystring-parser": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", - "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11573,8 +11463,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/service-error-classification": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", - "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11586,8 +11474,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/shared-ini-file-loader": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", - "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11600,8 +11486,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/signature-v4": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", - "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11620,8 +11504,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/smithy-client": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.6.tgz", - "integrity": "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11639,8 +11521,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/types": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11652,8 +11532,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/url-parser": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", - "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11667,8 +11545,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-base64": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11682,8 +11558,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-body-length-browser": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11695,8 +11569,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-body-length-node": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11708,8 +11580,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11722,8 +11592,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-config-provider": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11735,8 +11603,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-defaults-mode-browser": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.7.tgz", - "integrity": "sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11752,8 +11618,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-defaults-mode-node": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.7.tgz", - "integrity": "sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11771,8 +11635,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-endpoints": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", - "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11786,8 +11648,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-hex-encoding": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11799,8 +11659,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-middleware": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11813,8 +11671,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-retry": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", - "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11828,8 +11684,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-stream": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", - "integrity": "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11848,8 +11702,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11861,8 +11713,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/@smithy/util-utf8": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11875,8 +11725,6 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/ajv": { "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { @@ -11891,35 +11739,30 @@ } }, "node_modules/@aws/language-server-runtimes/node_modules/jose": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz", - "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==", + "version": "5.10.0", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } }, "node_modules/@aws/language-server-runtimes/node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, "node_modules/@aws/language-server-runtimes/node_modules/vscode-jsonrpc": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/@aws/language-server-runtimes/node_modules/vscode-languageserver": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", "dev": true, + "license": "MIT", "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, @@ -11929,19 +11772,19 @@ }, "node_modules/@aws/language-server-runtimes/node_modules/vscode-languageserver-protocol": { "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "dev": true, + "license": "MIT", "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "node_modules/@aws/mynah-ui": { - "version": "4.30.1", - "resolved": "https://registry.npmjs.org/@aws/mynah-ui/-/mynah-ui-4.30.1.tgz", - "integrity": "sha512-ZBtvmHYjlJXzIUCeDmNu1cFfJyO86S/+UCuM/LFbAV5mf4Qm1o8i0Gmpw/4ngKx3ZXdFGnVT1Iq2bCGSYhuoSw==", + "version": "4.30.3", + "resolved": "https://registry.npmjs.org/@aws/mynah-ui/-/mynah-ui-4.30.3.tgz", + "integrity": "sha512-Xy22dzCaFUqpdSHMpLa8Dsq98DiAUq49dm7Iu8Yj2YZXSCyfKQiYMJOfwU8IoqeNcEney5JRMJpf+/RysWugbA==", "hasInstallScript": true, + "license": "Apache License 2.0", "dependencies": { "escape-html": "^1.0.3", "highlight.js": "^11.11.0", @@ -12267,8 +12110,6 @@ }, "node_modules/@grpc/grpc-js": { "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.0.tgz", - "integrity": "sha512-pMuxInZjUnUkgMT2QLZclRqwk2ykJbIU05aZgPgJYXEpN9+2I7z7aNwcjWZSycRPl232FfhPszyBFJyOxTHNog==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12281,8 +12122,6 @@ }, "node_modules/@grpc/proto-loader": { "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12475,8 +12314,6 @@ }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", "dev": true, "license": "MIT", "funding": { @@ -12557,8 +12394,6 @@ }, "node_modules/@opentelemetry/api": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -12567,8 +12402,6 @@ }, "node_modules/@opentelemetry/api-logs": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12580,8 +12413,6 @@ }, "node_modules/@opentelemetry/context-async-hooks": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", - "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -12593,8 +12424,6 @@ }, "node_modules/@opentelemetry/core": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12609,8 +12438,6 @@ }, "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -12619,8 +12446,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.57.2.tgz", - "integrity": "sha512-eovEy10n3umjKJl2Ey6TLzikPE+W4cUQ4gCwgGP1RqzTGtgDra0WjIqdy29ohiUKfvmbiL3MndZww58xfIvyFw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12640,8 +12465,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-http": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.57.2.tgz", - "integrity": "sha512-0rygmvLcehBRp56NQVLSleJ5ITTduq/QfU7obOkyWgPpFHulwpw2LYTqNIz5TczKZuy5YY+5D3SDnXZL1tXImg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12660,8 +12483,6 @@ }, "node_modules/@opentelemetry/exporter-logs-otlp-proto": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.57.2.tgz", - "integrity": "sha512-ta0ithCin0F8lu9eOf4lEz9YAScecezCHkMMyDkvd9S7AnZNX5ikUmC5EQOQADU+oCcgo/qkQIaKcZvQ0TYKDw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12682,8 +12503,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.57.2.tgz", - "integrity": "sha512-r70B8yKR41F0EC443b5CGB4rUaOMm99I5N75QQt6sHKxYDzSEc6gm48Diz1CI1biwa5tDPznpylTrywO/pT7qw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12705,8 +12524,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-http": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.57.2.tgz", - "integrity": "sha512-ttb9+4iKw04IMubjm3t0EZsYRNWr3kg44uUuzfo9CaccYlOh8cDooe4QObDUkvx9d5qQUrbEckhrWKfJnKhemA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12725,8 +12542,6 @@ }, "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.57.2.tgz", - "integrity": "sha512-HX068Q2eNs38uf7RIkNN9Hl4Ynl+3lP0++KELkXMCpsCbFO03+0XNNZ1SkwxPlP9jrhQahsMPMkzNXpq3fKsnw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12746,8 +12561,6 @@ }, "node_modules/@opentelemetry/exporter-prometheus": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.57.2.tgz", - "integrity": "sha512-VqIqXnuxWMWE/1NatAGtB1PvsQipwxDcdG4RwA/umdBcW3/iOHp0uejvFHTRN2O78ZPged87ErJajyUBPUhlDQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12764,8 +12577,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.57.2.tgz", - "integrity": "sha512-gHU1vA3JnHbNxEXg5iysqCWxN9j83d7/epTYBZflqQnTyCC4N7yZXn/dMM+bEmyhQPGjhCkNZLx4vZuChH1PYw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12786,8 +12597,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.57.2.tgz", - "integrity": "sha512-sB/gkSYFu+0w2dVQ0PWY9fAMl172PKMZ/JrHkkW8dmjCL0CYkmXeE+ssqIL/yBUTPOvpLIpenX5T9RwXRBW/3g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12806,8 +12615,6 @@ }, "node_modules/@opentelemetry/exporter-trace-otlp-proto": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.57.2.tgz", - "integrity": "sha512-awDdNRMIwDvUtoRYxRhja5QYH6+McBLtoz1q9BeEsskhZcrGmH/V1fWpGx8n+Rc+542e8pJA6y+aullbIzQmlw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12826,8 +12633,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.30.1.tgz", - "integrity": "sha512-6S2QIMJahIquvFaaxmcwpvQQRD/YFaMTNoIxrfPIPOeITN+a8lfEcPDxNxn8JDAaxkg+4EnXhz8upVDYenoQjA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12845,8 +12650,6 @@ }, "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -12855,8 +12658,6 @@ }, "node_modules/@opentelemetry/instrumentation": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", - "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12876,8 +12677,6 @@ }, "node_modules/@opentelemetry/otlp-exporter-base": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.57.2.tgz", - "integrity": "sha512-XdxEzL23Urhidyebg5E6jZoaiW5ygP/mRjxLHixogbqwDy2Faduzb5N0o/Oi+XTIJu+iyxXdVORjXax+Qgfxag==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12893,8 +12692,6 @@ }, "node_modules/@opentelemetry/otlp-grpc-exporter-base": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.57.2.tgz", - "integrity": "sha512-USn173KTWy0saqqRB5yU9xUZ2xdgb1Rdu5IosJnm9aV4hMTuFFRTUsQxbgc24QxpCHeoKzzCSnS/JzdV0oM2iQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12912,8 +12709,6 @@ }, "node_modules/@opentelemetry/otlp-transformer": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.57.2.tgz", - "integrity": "sha512-48IIRj49gbQVK52jYsw70+Jv+JbahT8BqT2Th7C4H7RCM9d0gZ5sgNPoMpWldmfjvIsSgiGJtjfk9MeZvjhoig==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12934,8 +12729,6 @@ }, "node_modules/@opentelemetry/propagator-b3": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", - "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12950,8 +12743,6 @@ }, "node_modules/@opentelemetry/propagator-jaeger": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", - "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12966,8 +12757,6 @@ }, "node_modules/@opentelemetry/resources": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -12983,8 +12772,6 @@ }, "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -12993,8 +12780,6 @@ }, "node_modules/@opentelemetry/sdk-logs": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.57.2.tgz", - "integrity": "sha512-TXFHJ5c+BKggWbdEQ/inpgIzEmS2BGQowLE9UhsMd7YYlUfBQJ4uax0VF/B5NYigdM/75OoJGhAV3upEhK+3gg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13011,8 +12796,6 @@ }, "node_modules/@opentelemetry/sdk-metrics": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13028,8 +12811,6 @@ }, "node_modules/@opentelemetry/sdk-node": { "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.57.2.tgz", - "integrity": "sha512-8BaeqZyN5sTuPBtAoY+UtKwXBdqyuRKmekN5bFzAO40CgbGzAxfTpiL3PBerT7rhZ7p2nBdq7FaMv/tBQgHE4A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13063,8 +12844,6 @@ }, "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -13073,8 +12852,6 @@ }, "node_modules/@opentelemetry/sdk-trace-base": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13091,8 +12868,6 @@ }, "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -13101,8 +12876,6 @@ }, "node_modules/@opentelemetry/sdk-trace-node": { "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", - "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -13122,8 +12895,6 @@ }, "node_modules/@opentelemetry/semantic-conventions": { "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.30.0.tgz", - "integrity": "sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -13163,36 +12934,26 @@ }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -13202,36 +12963,26 @@ }, "node_modules/@protobufjs/float": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "dev": true, "license": "BSD-3-Clause" }, @@ -14366,13 +14117,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@tsconfig/node18": { - "version": "18.2.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", - "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/adm-zip": { "version": "0.4.34", "dev": true, @@ -14547,8 +14291,6 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, @@ -14571,9 +14313,8 @@ }, "node_modules/@types/lokijs": { "version": "1.5.14", - "resolved": "https://registry.npmjs.org/@types/lokijs/-/lokijs-1.5.14.tgz", - "integrity": "sha512-4Fic47BX3Qxr8pd12KT6/T1XWU8dOlJBIp1jGoMbaDbiEvdv50rAii+B3z1b/J2pvMywcVP+DBPGP5/lgLOKGA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/markdown-it": { "version": "13.0.2", @@ -14704,8 +14445,6 @@ }, "node_modules/@types/shimmer": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", "dev": true, "license": "MIT" }, @@ -16142,8 +15881,6 @@ }, "node_modules/axios": { "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "dev": true, "license": "MIT", "dependencies": { @@ -16865,8 +16602,6 @@ }, "node_modules/cjs-module-lexer": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true, "license": "MIT" }, @@ -18833,8 +18568,6 @@ }, "node_modules/fast-uri": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", "dev": true, "funding": [ { @@ -19611,8 +19344,6 @@ }, "node_modules/hpagent": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", - "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", "dev": true, "license": "MIT", "engines": { @@ -19905,8 +19636,6 @@ }, "node_modules/import-in-the-middle": { "version": "1.13.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz", - "integrity": "sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -20091,8 +19820,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -20147,8 +19874,6 @@ }, "node_modules/is-electron": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", "dev": true, "license": "MIT" }, @@ -21103,8 +20828,6 @@ }, "node_modules/lodash.camelcase": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true, "license": "MIT" }, @@ -21146,13 +20869,10 @@ }, "node_modules/lokijs": { "version": "1.5.12", - "resolved": "https://registry.npmjs.org/lokijs/-/lokijs-1.5.12.tgz", - "integrity": "sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q==" + "license": "MIT" }, "node_modules/long": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", - "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", "dev": true, "license": "Apache-2.0" }, @@ -21184,8 +20904,6 @@ }, "node_modules/mac-ca": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/mac-ca/-/mac-ca-3.1.1.tgz", - "integrity": "sha512-OmXW0O2HdZrL+CPbjvDJ68UxNdAtRfzzUaGqzRqwaFoU+BXlk6BFoJmNJSZv9wEAjMClIFoRA/GtGcbqgHY3kQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -21768,8 +21486,6 @@ }, "node_modules/module-details-from-path": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", - "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", "dev": true, "license": "MIT" }, @@ -22519,8 +22235,6 @@ }, "node_modules/pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", "engines": { @@ -22894,8 +22608,6 @@ }, "node_modules/protobufjs": { "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", @@ -22939,8 +22651,6 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true, "license": "MIT" }, @@ -23461,8 +23171,6 @@ }, "node_modules/require-in-the-middle": { "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -23476,8 +23184,6 @@ }, "node_modules/require-in-the-middle/node_modules/debug": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { @@ -23494,8 +23200,6 @@ }, "node_modules/require-in-the-middle/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, @@ -23506,8 +23210,6 @@ }, "node_modules/resolve": { "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { @@ -23686,8 +23388,6 @@ }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -24084,8 +23784,6 @@ }, "node_modules/shimmer": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", "dev": true, "license": "BSD-2-Clause" }, @@ -25173,8 +24871,6 @@ }, "node_modules/typescript": { "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "license": "Apache-2.0", "bin": { @@ -25243,8 +24939,6 @@ }, "node_modules/undici": { "version": "6.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", - "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", "dev": true, "license": "MIT", "engines": { @@ -25525,8 +25219,7 @@ }, "node_modules/vscode-languageserver-textdocument": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" + "license": "MIT" }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", @@ -26216,8 +25909,6 @@ }, "node_modules/win-ca": { "version": "3.5.1", - "resolved": "https://registry.npmjs.org/win-ca/-/win-ca-3.5.1.tgz", - "integrity": "sha512-RNy9gpBS6cxWHjfbqwBA7odaHyT+YQNhtdpJZwYCFoxB/Dq22oeOZ9YCXMwjhLytKpo7JJMnKdJ/ve7N12zzfQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -26230,8 +25921,6 @@ }, "node_modules/win-ca/node_modules/make-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -26694,7 +26383,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.61.0-SNAPSHOT", + "version": "1.67.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" @@ -26737,7 +26426,7 @@ "@aws-sdk/s3-request-presigner": "<3.731.0", "@aws-sdk/smithy-client": "<3.731.0", "@aws-sdk/util-arn-parser": "<3.731.0", - "@aws/mynah-ui": "^4.30.1", + "@aws/mynah-ui": "^4.30.3", "@gerhobbelt/gitignore-parser": "^0.2.0-9", "@iarna/toml": "^2.2.5", "@smithy/fetch-http-handler": "^5.0.1", @@ -26792,9 +26481,10 @@ }, "devDependencies": { "@aws-sdk/types": "^3.13.1", - "@aws/chat-client-ui-types": "^0.1.12", - "@aws/language-server-runtimes": "^0.2.58", - "@aws/language-server-runtimes-types": "^0.1.13", + "@aws/chat-client": "^0.1.4", + "@aws/chat-client-ui-types": "^0.1.24", + "@aws/language-server-runtimes": "^0.2.70", + "@aws/language-server-runtimes-types": "^0.1.26", "@cspotcode/source-map-support": "^0.8.1", "@sinonjs/fake-timers": "^10.0.2", "@types/adm-zip": "^0.4.34", @@ -26970,8 +26660,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb-elastic/node_modules/@smithy/middleware-retry": { "version": "3.0.34", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.34.tgz", - "integrity": "sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.12", @@ -26990,8 +26678,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb-elastic/node_modules/@smithy/node-http-handler": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", - "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.9", @@ -27006,8 +26692,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb-elastic/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27019,8 +26703,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb-elastic/node_modules/@smithy/service-error-classification": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", - "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2" @@ -27031,8 +26713,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb-elastic/node_modules/@smithy/util-retry": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", - "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.11", @@ -27056,8 +26736,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb/node_modules/@smithy/middleware-retry": { "version": "3.0.34", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.34.tgz", - "integrity": "sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.12", @@ -27076,8 +26754,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb/node_modules/@smithy/node-http-handler": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", - "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.9", @@ -27092,8 +26768,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27105,8 +26779,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb/node_modules/@smithy/service-error-classification": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", - "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2" @@ -27117,8 +26789,6 @@ }, "packages/core/node_modules/@aws-sdk/client-docdb/node_modules/@smithy/util-retry": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", - "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.11", @@ -27240,8 +26910,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-retry": { "version": "3.0.34", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.34.tgz", - "integrity": "sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.12", @@ -27260,8 +26928,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", - "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.9", @@ -27276,8 +26942,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27289,8 +26953,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/service-error-classification": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", - "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2" @@ -27301,8 +26963,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-retry": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", - "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.11", @@ -27326,8 +26986,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-retry": { "version": "3.0.34", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.34.tgz", - "integrity": "sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.12", @@ -27346,8 +27004,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", - "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.9", @@ -27362,8 +27018,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27375,8 +27029,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso/node_modules/@smithy/service-error-classification": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", - "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2" @@ -27387,8 +27039,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-retry": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", - "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.11", @@ -27461,8 +27111,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sts/node_modules/@smithy/middleware-retry": { "version": "3.0.34", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.34.tgz", - "integrity": "sha512-yVRr/AAtPZlUvwEkrq7S3x7Z8/xCd97m2hLDaqdz6ucP2RKHsBjEqaUA2ebNv2SsZoPEi+ZD0dZbOB1u37tGCA==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^3.1.12", @@ -27481,8 +27129,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sts/node_modules/@smithy/node-http-handler": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", - "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.9", @@ -27497,8 +27143,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sts/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27510,8 +27154,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sts/node_modules/@smithy/service-error-classification": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.11.tgz", - "integrity": "sha512-QnYDPkyewrJzCyaeI2Rmp7pDwbUETe+hU8ADkXmgNusO1bgHBH7ovXJiYmba8t0fNfJx75fE8dlM6SEmZxheog==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2" @@ -27522,8 +27164,6 @@ }, "packages/core/node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-retry": { "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.11.tgz", - "integrity": "sha512-hJUC6W7A3DQgaee3Hp9ZFcOxVDZzmBIRBPlUAk8/fSOEl7pE/aX7Dci0JycNOnm9Mfr0KV2XjIlUOcGWXQUdVQ==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^3.0.11", @@ -27556,8 +27196,6 @@ }, "packages/core/node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27599,8 +27237,6 @@ }, "packages/core/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.3.3.tgz", - "integrity": "sha512-BrpZOaZ4RCbcJ2igiSNG16S+kgAc65l/2hmxWdmhyoGWHTLlzQzr06PXavJp9OBlPEG/sHlqdxjWmjzV66+BSQ==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^3.1.9", @@ -27615,8 +27251,6 @@ }, "packages/core/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27652,8 +27286,6 @@ }, "packages/core/node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.12", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", - "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27686,8 +27318,6 @@ }, "packages/core/node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.12", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", - "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27716,8 +27346,6 @@ }, "packages/core/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.12", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", - "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27759,8 +27387,6 @@ }, "packages/core/node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27797,8 +27423,6 @@ }, "packages/core/node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27826,8 +27450,6 @@ }, "packages/core/node_modules/@aws-sdk/middleware-sdk-rds/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27855,8 +27477,6 @@ }, "packages/core/node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.8.tgz", - "integrity": "sha512-hmgIAVyxw1LySOwkgMIUN0kjN8TG9Nc85LJeEmEE/cNEe2rkHDUWhnJf2gxcSRFLWsyqWsrZGw40ROjUogg+Iw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27900,8 +27520,6 @@ }, "packages/core/node_modules/@aws-sdk/token-providers/node_modules/@smithy/shared-ini-file-loader": { "version": "3.1.12", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.12.tgz", - "integrity": "sha512-1xKSGI+U9KKdbG2qDvIR9dGrw3CNx+baqJfyr0igKEpjbHL5stsqAesYBzHChYHlelWtb87VnLWlhvfCz13H8Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^3.7.2", @@ -27958,8 +27576,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", "license": "Apache-2.0", "dependencies": { "@smithy/protocol-http": "^5.1.0", @@ -27974,8 +27590,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -27986,8 +27600,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler/node_modules/@smithy/querystring-builder": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28000,8 +27612,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28012,8 +27622,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-base64": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -28026,8 +27634,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", @@ -28039,8 +27645,6 @@ }, "packages/core/node_modules/@smithy/fetch-http-handler/node_modules/@smithy/util-utf8": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -28062,8 +27666,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.0.2", @@ -28082,8 +27684,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/core": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.0.3", @@ -28101,8 +27701,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28113,8 +27711,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/middleware-endpoint": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.2.0", @@ -28132,8 +27728,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/middleware-serde": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28145,8 +27739,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/middleware-stack": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28158,8 +27750,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/node-config-provider": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.0.2", @@ -28173,8 +27763,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/property-provider": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28186,8 +27774,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/smithy-client": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", "license": "Apache-2.0", "dependencies": { "@smithy/core": "^3.2.0", @@ -28204,8 +27790,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28216,8 +27800,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/url-parser": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", "license": "Apache-2.0", "dependencies": { "@smithy/querystring-parser": "^4.0.2", @@ -28230,8 +27812,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/util-base64": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -28244,8 +27824,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/util-body-length-browser": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28256,8 +27834,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", @@ -28269,8 +27845,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/util-middleware": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28282,8 +27856,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/util-stream": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", "license": "Apache-2.0", "dependencies": { "@smithy/fetch-http-handler": "^5.0.2", @@ -28301,8 +27873,6 @@ }, "packages/core/node_modules/@smithy/middleware-retry/node_modules/@smithy/util-utf8": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -28314,8 +27884,6 @@ }, "packages/core/node_modules/@smithy/node-http-handler": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", "license": "Apache-2.0", "dependencies": { "@smithy/abort-controller": "^4.0.2", @@ -28330,8 +27898,6 @@ }, "packages/core/node_modules/@smithy/node-http-handler/node_modules/@smithy/abort-controller": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28343,8 +27909,6 @@ }, "packages/core/node_modules/@smithy/node-http-handler/node_modules/@smithy/querystring-builder": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28357,8 +27921,6 @@ }, "packages/core/node_modules/@smithy/node-http-handler/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28369,8 +27931,6 @@ }, "packages/core/node_modules/@smithy/protocol-http": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28382,8 +27942,6 @@ }, "packages/core/node_modules/@smithy/protocol-http/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28394,8 +27952,6 @@ }, "packages/core/node_modules/@smithy/querystring-parser": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28407,8 +27963,6 @@ }, "packages/core/node_modules/@smithy/querystring-parser/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28419,8 +27973,6 @@ }, "packages/core/node_modules/@smithy/service-error-classification": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0" @@ -28431,8 +27983,6 @@ }, "packages/core/node_modules/@smithy/service-error-classification/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28443,8 +27993,6 @@ }, "packages/core/node_modules/@smithy/shared-ini-file-loader": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.2.0", @@ -28456,8 +28004,6 @@ }, "packages/core/node_modules/@smithy/shared-ini-file-loader/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28479,8 +28025,6 @@ }, "packages/core/node_modules/@smithy/util-hex-encoding": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28491,8 +28035,6 @@ }, "packages/core/node_modules/@smithy/util-retry": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", "license": "Apache-2.0", "dependencies": { "@smithy/service-error-classification": "^4.0.2", @@ -28505,8 +28047,6 @@ }, "packages/core/node_modules/@smithy/util-retry/node_modules/@smithy/types": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28517,8 +28057,6 @@ }, "packages/core/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -28559,7 +28097,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.56.0-SNAPSHOT", + "version": "3.61.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" @@ -28638,6 +28176,18 @@ "node": ">=18.0.0" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "license": "Apache-2.0", @@ -28745,8 +28295,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/client-sso": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.731.0.tgz", - "integrity": "sha512-O4C/UYGgqMsBg21MMApFdgyh8BX568hQhbdoNFmRVTBoSnCZ3w+H4a1wBPX4Gyl0NX+ab6Xxo9rId8HiyPXJ0A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -28794,8 +28342,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/core": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.731.0.tgz", - "integrity": "sha512-ithBN1VWASkvAIlozJmenqDvNnFddr/SZXAs58+jCnBHgy3tXLHABZGVNCjetZkHRqNdXEO1kirnoxaFeXMeDA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -28816,8 +28362,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-env": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.731.0.tgz", - "integrity": "sha512-h0WWZg4QMLgFVyIvQrC43zpVqsUWg1mPM1clpogP43B8+wEhDEQ4qWRzvFs3dQ4cqx/FLyDUZZF4cqgd94z7kw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -28832,8 +28376,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-http": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.731.0.tgz", - "integrity": "sha512-iRtrjtcYaWgbvtu2cvDhIsPWXZGvhy1Hgks4682MEBNTc9AUwlfvDrYz2EEnTtJJyrbOdEHVrYrzqD8qPyVLCg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -28853,8 +28395,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.731.1.tgz", - "integrity": "sha512-0M0ejuqW8iHNcTH2ZXSY9m+I7Y06qVkj6k3vfQU9XaB//mTUCxxfGfqWAtgfr7Yi73egABTcPc0jyPdcvSW4Kw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -28877,8 +28417,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-node": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.731.1.tgz", - "integrity": "sha512-5c0ZiagMTPmWilXNffeXJCLoCEz97jilHr3QJWwf2GaTay4tzN+Ld71rpdfEenzUR7fuxEWFfVlwQbFOzFNYHg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.731.0", @@ -28900,8 +28438,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-process": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.731.0.tgz", - "integrity": "sha512-6yNMY6q3xHLbs2f2+C6GhvMrjTgtFBiPJJqKaPLsTIhlTRvh4sK8pGm3ITcma0jOxtPDIuoPfBAV8N8XVMBlZg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -28917,8 +28453,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.731.1.tgz", - "integrity": "sha512-p1tp+rMUf5YNQLr8rVRmDgNtKGYLL0KCdq3K2hwwvFnx9MjReF1sA4lfm3xWsxBQM+j3QN9AvMQqBzDJ+NOSdw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.731.0", @@ -28936,8 +28470,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.731.1.tgz", - "integrity": "sha512-+ynAvEGWDR5ZJFxgpwwzhvlQ3WQ7BleWXU6JwpIw3yFrD4eZEn85b8DZC1aEz7C9kb1HSV6B3gpqHqlyS6wj8g==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -28953,8 +28485,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-host-header": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.731.0.tgz", - "integrity": "sha512-ndAJsm5uWPPJRZowLKpB1zuL17qWlWVtCJP4I/ynBkq1PU1DijDXBul2UZaG6Mpvsgms1NXo/h9noHuK7T3v8w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -28968,8 +28498,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-logger": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.731.0.tgz", - "integrity": "sha512-IIZrOdjbY2vKzPJPrwE7FoFQCIPEL6UqURi8LEaiVyCag4p2fvaTN5pgKuQtGC2+iYd/HHcGT4qn2bAqF5Jmmw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -28982,8 +28510,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.731.0.tgz", - "integrity": "sha512-y6FLASB1iKWuR5tUipMyo77bt0lEl3OnCrrd2xw/H24avq1HhJjjPR0HHhJE6QKJzF/FYXeV88tcyPSMe32VDw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -28997,8 +28523,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.731.0.tgz", - "integrity": "sha512-Ngr2Gz0aec/uduoKaO3srN52SYkEHndYtFzkK/gDUyQwQzi4ha2eIisxPiuHEX6RvXT31V9ouqn/YtVkt0R76A==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -29015,8 +28539,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/nested-clients": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.731.1.tgz", - "integrity": "sha512-/L8iVrulnXZl+kgmTn+oxRxNnhcSIbf+r12C06vGUq60w0YMidLvxJZN7vt8H9SnCAGCHqud2MS7ExCEvhc0gA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -29064,8 +28586,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/region-config-resolver": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.731.0.tgz", - "integrity": "sha512-XlDpRNkDVHF59f07JmkuAidEv//m3hT6/JL85h0l3+zrpaRWhf8n8lVUyAPNq35ZujK8AcorYM+93u7hdWsliQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -29081,8 +28601,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/token-providers": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.731.1.tgz", - "integrity": "sha512-t34GOPwBZsX7zGHjiTXmMHGY3kHM7fLiQ60Jqk0On9P0ASHTDE5U75RgCXboE3u+qEv9wyKyaqMNyMWj9qQlFg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/nested-clients": "3.731.1", @@ -29098,8 +28616,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/types": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.0.0", @@ -29111,8 +28627,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-endpoints": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", - "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -29125,19 +28639,17 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-locate-window": { - "version": "3.693.0", + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.731.0.tgz", - "integrity": "sha512-EnYXxTkCNCjTTBjW/pelRPv4Thsi9jepoB6qQjPMA9/ixrZ71BhhQecz9kgqzZLR9BPCwb6hgJ/Yd702jqJ4aQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -29148,8 +28660,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.731.0.tgz", - "integrity": "sha512-Rze78Ym5Bx7aWMvmZE2iL3JPo2INNCC5N9rLVx98Gg1G0ZaxclVRUvJrh1AojNlOFxU+otkxAe7FA3Foy2iLLQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.731.0", @@ -29171,7 +28681,7 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@babel/runtime": { - "version": "7.26.0", + "version": "7.26.9", "dev": true, "license": "MIT", "dependencies": { @@ -29182,12 +28692,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29195,15 +28703,13 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { @@ -29211,17 +28717,15 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", + "version": "3.1.5", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -29230,15 +28734,13 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", "tslib": "^2.6.2" }, "engines": { @@ -29246,13 +28748,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/eventstream-codec": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.2.tgz", - "integrity": "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" }, @@ -29261,13 +28761,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/eventstream-serde-browser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.2.tgz", - "integrity": "sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29275,12 +28773,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.0.tgz", - "integrity": "sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29288,13 +28784,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/eventstream-serde-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.2.tgz", - "integrity": "sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29302,13 +28796,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/eventstream-serde-universal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.2.tgz", - "integrity": "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/eventstream-codec": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29316,14 +28808,12 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, @@ -29332,12 +28822,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -29347,12 +28835,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29361,8 +28847,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29372,13 +28856,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29386,18 +28868,16 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", + "version": "4.0.6", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", + "@smithy/core": "^3.1.5", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { @@ -29405,18 +28885,16 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", + "version": "4.0.7", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -29425,12 +28903,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "version": "4.0.2", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29438,12 +28914,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29451,14 +28925,12 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/node-config-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29466,15 +28938,13 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "version": "4.0.3", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/abort-controller": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29482,12 +28952,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/property-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29495,12 +28963,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29508,12 +28974,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, @@ -29522,12 +28986,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29535,24 +28997,20 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0" + "@smithy/types": "^4.1.0" }, "engines": { "node": ">=18.0.0" } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29560,16 +29018,14 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/signature-v4": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.2.tgz", - "integrity": "sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.1", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -29579,17 +29035,15 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/smithy-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", + "version": "4.1.6", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", + "@smithy/core": "^3.1.5", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { @@ -29597,9 +29051,7 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "version": "4.1.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29609,13 +29061,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29624,8 +29074,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-base64": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -29638,8 +29086,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-body-length-browser": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29650,8 +29096,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-body-length-node": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29662,8 +29106,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", @@ -29675,8 +29117,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-config-provider": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29686,14 +29126,12 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", - "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", + "version": "4.0.7", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -29702,17 +29140,15 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", - "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", + "version": "4.0.7", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29720,13 +29156,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "version": "3.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29735,8 +29169,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-hex-encoding": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29746,12 +29178,10 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29759,13 +29189,11 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -29773,14 +29201,12 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "version": "4.1.2", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", @@ -29793,8 +29219,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -29805,8 +29229,6 @@ }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@smithy/util-utf8": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -29816,10 +29238,13 @@ "node": ">=18.0.0" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@tsconfig/node18": { + "version": "18.2.4", + "dev": true, + "license": "MIT" + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/@types/node": { - "version": "18.19.83", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.83.tgz", - "integrity": "sha512-D69JeR5SfFS5H6FLbUaS0vE4r1dGhmMBbG4Ed6BNS4wkDK8GZjsdCShT5LCN59vOHEUHnFCY9J4aclXlIphMkA==", + "version": "18.19.80", "dev": true, "license": "MIT", "dependencies": { @@ -29857,6 +29282,10 @@ "dev": true, "license": "MIT" }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/bowser": { + "version": "2.11.0", + "license": "MIT" + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -29973,7 +29402,7 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/downlevel-dts/node_modules/typescript": { - "version": "5.8.0-dev.20250129", + "version": "5.9.0-dev.20250324", "dev": true, "license": "Apache-2.0", "bin": { @@ -30022,6 +29451,14 @@ "dev": true, "license": "ISC" }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/get-caller-file": { "version": "2.0.5", "dev": true, @@ -30057,6 +29494,17 @@ "node": ">=8" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/hasown": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/inflight": { "version": "1.0.6", "dev": true, @@ -30079,6 +29527,20 @@ "node": ">= 0.10" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/is-core-module": { + "version": "2.16.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "dev": true, @@ -30119,6 +29581,11 @@ "node": ">=0.10.0" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/rechoir": { "version": "0.6.2", "dev": true, @@ -30142,6 +29609,25 @@ "node": ">=0.10.0" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/resolve": { + "version": "1.22.10", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/rimraf": { "version": "3.0.2", "dev": true, @@ -30173,7 +29659,7 @@ "license": "0BSD" }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "dev": true, "license": "ISC", "bin": { @@ -30228,7 +29714,13 @@ } }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/strnum": { - "version": "1.0.5", + "version": "1.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT" }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/supports-color": { @@ -30245,6 +29737,17 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/tree-kill": { "version": "1.2.2", "dev": true, @@ -30257,10 +29760,20 @@ "version": "2.8.1", "license": "0BSD" }, + "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/typescript": { + "version": "5.2.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "src.gen/@amzn/amazon-q-developer-streaming-client/node_modules/undici-types": { "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, @@ -30387,6 +29900,18 @@ "node": ">=18.0.0" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "license": "Apache-2.0", @@ -30494,8 +30019,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/core": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.731.0.tgz", - "integrity": "sha512-ithBN1VWASkvAIlozJmenqDvNnFddr/SZXAs58+jCnBHgy3tXLHABZGVNCjetZkHRqNdXEO1kirnoxaFeXMeDA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30516,8 +30039,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/middleware-host-header": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.731.0.tgz", - "integrity": "sha512-ndAJsm5uWPPJRZowLKpB1zuL17qWlWVtCJP4I/ynBkq1PU1DijDXBul2UZaG6Mpvsgms1NXo/h9noHuK7T3v8w==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30531,8 +30052,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/middleware-logger": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.731.0.tgz", - "integrity": "sha512-IIZrOdjbY2vKzPJPrwE7FoFQCIPEL6UqURi8LEaiVyCag4p2fvaTN5pgKuQtGC2+iYd/HHcGT4qn2bAqF5Jmmw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30545,8 +30064,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.731.0.tgz", - "integrity": "sha512-y6FLASB1iKWuR5tUipMyo77bt0lEl3OnCrrd2xw/H24avq1HhJjjPR0HHhJE6QKJzF/FYXeV88tcyPSMe32VDw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30560,8 +30077,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.731.0.tgz", - "integrity": "sha512-Ngr2Gz0aec/uduoKaO3srN52SYkEHndYtFzkK/gDUyQwQzi4ha2eIisxPiuHEX6RvXT31V9ouqn/YtVkt0R76A==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.731.0", @@ -30578,8 +30093,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/nested-clients": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.731.1.tgz", - "integrity": "sha512-/L8iVrulnXZl+kgmTn+oxRxNnhcSIbf+r12C06vGUq60w0YMidLvxJZN7vt8H9SnCAGCHqud2MS7ExCEvhc0gA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -30627,8 +30140,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/region-config-resolver": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.731.0.tgz", - "integrity": "sha512-XlDpRNkDVHF59f07JmkuAidEv//m3hT6/JL85h0l3+zrpaRWhf8n8lVUyAPNq35ZujK8AcorYM+93u7hdWsliQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30644,8 +30155,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/token-providers": { "version": "3.731.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.731.1.tgz", - "integrity": "sha512-t34GOPwBZsX7zGHjiTXmMHGY3kHM7fLiQ60Jqk0On9P0ASHTDE5U75RgCXboE3u+qEv9wyKyaqMNyMWj9qQlFg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/nested-clients": "3.731.1", @@ -30661,8 +30170,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/types": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.731.0.tgz", - "integrity": "sha512-NrdkJg6oOUbXR2r9WvHP408CLyvST8cJfp1/jP9pemtjvjPoh6NukbCtiSFdOOb1eryP02CnqQWItfJC1p2Y/Q==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.0.0", @@ -30674,8 +30181,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/util-endpoints": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.731.0.tgz", - "integrity": "sha512-riztxTAfncFS9yQWcBJffGgOgLoKSa63ph+rxWJxKl6BHAmWEvHICj1qDcVmnWfIcvJ5cClclY75l9qKaUH7rQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30688,19 +30193,17 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/util-locate-window": { - "version": "3.693.0", + "version": "3.723.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.731.0.tgz", - "integrity": "sha512-EnYXxTkCNCjTTBjW/pelRPv4Thsi9jepoB6qQjPMA9/ixrZ71BhhQecz9kgqzZLR9BPCwb6hgJ/Yd702jqJ4aQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.731.0", @@ -30711,8 +30214,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.731.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.731.0.tgz", - "integrity": "sha512-Rze78Ym5Bx7aWMvmZE2iL3JPo2INNCC5N9rLVx98Gg1G0ZaxclVRUvJrh1AojNlOFxU+otkxAe7FA3Foy2iLLQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.731.0", @@ -30734,7 +30235,7 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@babel/runtime": { - "version": "7.26.0", + "version": "7.26.9", "dev": true, "license": "MIT", "dependencies": { @@ -30745,12 +30246,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/abort-controller": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.2.tgz", - "integrity": "sha512-Sl/78VDtgqKxN2+1qduaVE140XF+Xg+TafkncspwM4jFP/LHr76ZHmIY/y3V1M0mMLNk+Je6IGbzxy23RSToMw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30758,15 +30257,13 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.0.tgz", - "integrity": "sha512-8smPlwhga22pwl23fM5ew4T9vfLUCeFXlcqNOCD5M5h8VmNPNUE9j6bQSuRXpDSV11L/E/SwEBQuW8hr6+nS1A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { @@ -30774,17 +30271,15 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.2.0.tgz", - "integrity": "sha512-k17bgQhVZ7YmUvA8at4af1TDpl0NDMBuBKJl8Yg0nrefwmValU+CnA5l/AriVdQNthU/33H3nK71HrLgqOPr1Q==", + "version": "3.1.5", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.3", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-stream": "^4.2.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-stream": "^4.1.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -30793,15 +30288,13 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.2.tgz", - "integrity": "sha512-32lVig6jCaWBHnY+OEQ6e6Vnt5vDHaLiydGrwYMW9tPqO688hPGTYRamYJ1EptxEC2rAwJrHWmPoKRBl4iTa8w==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", "tslib": "^2.6.2" }, "engines": { @@ -30809,13 +30302,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/eventstream-codec": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.0.2.tgz", - "integrity": "sha512-p+f2kLSK7ZrXVfskU/f5dzksKTewZk8pJLPvER3aFHPt76C2MxD9vNatSfLzzQSQB4FNO96RK4PSXfhD1TTeMQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", "tslib": "^2.6.2" }, @@ -30824,13 +30315,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/eventstream-serde-browser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.0.2.tgz", - "integrity": "sha512-CepZCDs2xgVUtH7ZZ7oDdZFH8e6Y2zOv8iiX6RhndH69nlojCALSKK+OXwZUgOtUZEUaZ5e1hULVCHYbCn7pug==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30838,12 +30327,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.1.0.tgz", - "integrity": "sha512-1PI+WPZ5TWXrfj3CIoKyUycYynYJgZjuQo8U+sphneOtjsgrttYybdqESFReQrdWJ+LKt6NEdbYzmmfDBmjX2A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30851,13 +30338,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/eventstream-serde-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.0.2.tgz", - "integrity": "sha512-C5bJ/C6x9ENPMx2cFOirspnF9ZsBVnBMtP6BdPl/qYSuUawdGQ34Lq0dMcf42QTjUZgWGbUIZnz6+zLxJlb9aw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/eventstream-serde-universal": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30865,13 +30350,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/eventstream-serde-universal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.0.2.tgz", - "integrity": "sha512-St8h9JqzvnbB52FtckiHPN4U/cnXcarMniXRXTKn0r4b4XesZOGiAyUdj1aXbqqn1icSqBlzzUsCl6nPB018ng==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/eventstream-codec": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30879,14 +30362,12 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.2.tgz", - "integrity": "sha512-+9Dz8sakS9pe7f2cBocpJXdeVjMopUDLgZs1yWeu7h++WqSbjUYv/JAJwKwXw1HV6gq1jyWjxuyn24E2GhoEcQ==", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, @@ -30895,12 +30376,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/hash-node": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.2.tgz", - "integrity": "sha512-VnTpYPnRUE7yVhWozFdlxcYknv9UN7CeOqSrMH+V877v4oqtVYuoqhIhtSjmGPvYrYnAkaM61sLMKHvxL138yg==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -30910,12 +30389,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/invalid-dependency": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.2.tgz", - "integrity": "sha512-GatB4+2DTpgWPday+mnUkoumP54u/MDM/5u44KF9hIu8jF0uafZtQLcdfIKkIcUNuF/fBojpLEHZS/56JqPeXQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30924,8 +30401,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -30935,13 +30410,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/middleware-content-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.2.tgz", - "integrity": "sha512-hAfEXm1zU+ELvucxqQ7I8SszwQ4znWMbNv6PLMndN83JJN41EPuS93AIyh2N+gJ6x8QFhzSO6b7q2e6oClDI8A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -30949,18 +30422,16 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.0.tgz", - "integrity": "sha512-xhLimgNCbCzsUppRTGXWkZywksuTThxaIB0HwbpsVLY5sceac4e1TZ/WKYqufQLaUy+gUSJGNdwD2jo3cXL0iA==", + "version": "4.0.6", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-serde": "^4.0.3", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", - "@smithy/url-parser": "^4.0.2", - "@smithy/util-middleware": "^4.0.2", + "@smithy/core": "^3.1.5", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { @@ -30968,18 +30439,16 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/middleware-retry": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.0.tgz", - "integrity": "sha512-2zAagd1s6hAaI/ap6SXi5T3dDwBOczOMCSkkYzktqN1+tzbk1GAsHNAdo/1uzxz3Ky02jvZQwbi/vmDA6z4Oyg==", + "version": "4.0.7", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/service-error-classification": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", - "@smithy/util-middleware": "^4.0.2", - "@smithy/util-retry": "^4.0.2", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -30988,12 +30457,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/middleware-serde": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.3.tgz", - "integrity": "sha512-rfgDVrgLEVMmMn0BI8O+8OVr6vXzjV7HZj57l0QxslhzbvVfikZbVfBVthjLHqib4BW44QhcIgJpvebHlRaC9A==", + "version": "4.0.2", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31001,12 +30468,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/middleware-stack": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.2.tgz", - "integrity": "sha512-eSPVcuJJGVYrFYu2hEq8g8WWdJav3sdrI4o2c6z/rjnYDd3xH9j9E7deZQCzFn4QvGPouLngH3dQ+QVTxv5bOQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31014,14 +30479,12 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/node-config-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.2.tgz", - "integrity": "sha512-WgCkILRZfJwJ4Da92a6t3ozN/zcvYyJGUTmfGbgS/FkCcoCjl7G4FJaCDN1ySdvLvemnQeo25FdkyMSTSwulsw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/shared-ini-file-loader": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31029,15 +30492,13 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/node-http-handler": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.4.tgz", - "integrity": "sha512-/mdqabuAT3o/ihBGjL94PUbTSPSRJ0eeVTdgADzow0wRJ0rN4A27EOrtlK56MYiO1fDvlO3jVTCxQtQmK9dZ1g==", + "version": "4.0.3", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/querystring-builder": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/abort-controller": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/querystring-builder": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31045,12 +30506,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/property-provider": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.2.tgz", - "integrity": "sha512-wNRoQC1uISOuNc2s4hkOYwYllmiyrvVXWMtq+TysNRVQaHm4yoafYQyjN/goYZS+QbYlPIbb/QRjaUZMuzwQ7A==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31058,12 +30517,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/protocol-http": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.0.tgz", - "integrity": "sha512-KxAOL1nUNw2JTYrtviRRjEnykIDhxc84qMBzxvu1MUfQfHTuBlCG7PA6EdVwqpJjH7glw7FqQoFxUJSyBQgu7g==", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31071,12 +30528,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/querystring-builder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.2.tgz", - "integrity": "sha512-NTOs0FwHw1vimmQM4ebh+wFQvOwkEf/kQL6bSM1Lock+Bv4I89B3hGYoUEPkmvYPkDKyp5UdXJYu+PoTQ3T31Q==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, @@ -31085,12 +30540,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/querystring-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.2.tgz", - "integrity": "sha512-v6w8wnmZcVXjfVLjxw8qF7OwESD9wnpjp0Dqry/Pod0/5vcEA3qxCr+BhbOHlxS8O+29eLpT3aagxXGwIoEk7Q==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31098,24 +30551,20 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/service-error-classification": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.2.tgz", - "integrity": "sha512-LA86xeFpTKn270Hbkixqs5n73S+LVM0/VZco8dqd+JT75Dyx3Lcw/MraL7ybjmz786+160K8rPOmhsq0SocoJQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0" + "@smithy/types": "^4.1.0" }, "engines": { "node": ">=18.0.0" } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.2.tgz", - "integrity": "sha512-J9/gTWBGVuFZ01oVA6vdb4DAjf1XbDhK6sLsu3OS9qmLrS6KB5ygpeHiM3miIbj1qgSJ96GYszXFWv6ErJ8QEw==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31123,16 +30572,14 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/signature-v4": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.2.tgz", - "integrity": "sha512-Mz+mc7okA73Lyz8zQKJNyr7lIcHLiPYp0+oiqiMNc/t7/Kf2BENs5d63pEj7oPqdjaum6g0Fc8wC78dY1TgtXw==", + "version": "5.0.1", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.2", + "@smithy/util-middleware": "^4.0.1", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -31142,17 +30589,15 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/smithy-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.2.0.tgz", - "integrity": "sha512-Qs65/w30pWV7LSFAez9DKy0Koaoh3iHhpcpCCJ4waj/iqwsuSzJna2+vYwq46yBaqO5ZbP9TjUsATUNxrKeBdw==", + "version": "4.1.6", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.2.0", - "@smithy/middleware-endpoint": "^4.1.0", - "@smithy/middleware-stack": "^4.0.2", - "@smithy/protocol-http": "^5.1.0", - "@smithy/types": "^4.2.0", - "@smithy/util-stream": "^4.2.0", + "@smithy/core": "^3.1.5", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/types": "^4.1.0", + "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { @@ -31160,9 +30605,7 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.2.0.tgz", - "integrity": "sha512-7eMk09zQKCO+E/ivsjQv+fDlOupcFUCSC/L2YUPgwhvowVGWbPQHjEFcmjt7QQ4ra5lyowS92SV53Zc6XD4+fg==", + "version": "4.1.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -31172,13 +30615,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/url-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.2.tgz", - "integrity": "sha512-Bm8n3j2ScqnT+kJaClSVCMeiSenK6jVAzZCNewsYWuZtnBehEz4r2qP0riZySZVfzB+03XZHJeqfmJDkeeSLiQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/querystring-parser": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31187,8 +30628,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-base64": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -31201,8 +30640,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-body-length-browser": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -31213,8 +30650,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-body-length-node": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -31225,8 +30660,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", @@ -31238,8 +30671,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-config-provider": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -31249,14 +30680,12 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.8.tgz", - "integrity": "sha512-ZTypzBra+lI/LfTYZeop9UjoJhhGRTg3pxrNpfSTQLd3AJ37r2z4AXTKpq1rFXiiUIJsYyFgNJdjWRGP/cbBaQ==", + "version": "4.0.7", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -31265,17 +30694,15 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.8.tgz", - "integrity": "sha512-Rgk0Jc/UDfRTzVthye/k2dDsz5Xxs9LZaKCNPgJTRyoyBoeiNCnHsYGOyu1PKN+sDyPnJzMOz22JbwxzBp9NNA==", + "version": "4.0.7", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.0", - "@smithy/credential-provider-imds": "^4.0.2", - "@smithy/node-config-provider": "^4.0.2", - "@smithy/property-provider": "^4.0.2", - "@smithy/smithy-client": "^4.2.0", - "@smithy/types": "^4.2.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/credential-provider-imds": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31283,13 +30710,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-endpoints": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.2.tgz", - "integrity": "sha512-6QSutU5ZyrpNbnd51zRTL7goojlcnuOB55+F9VBD+j8JpRY50IGamsjlycrmpn8PQkmJucFW8A0LSfXj7jjtLQ==", + "version": "3.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31298,8 +30723,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-hex-encoding": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -31309,12 +30732,10 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-middleware": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.2.tgz", - "integrity": "sha512-6GDamTGLuBQVAEuQ4yDQ+ti/YINf/MEmIegrEeg7DdB/sld8BX1lqt9RRuIcABOhAGTA50bRbPzErez7SlDtDQ==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.2.0", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31322,13 +30743,11 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-retry": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.2.tgz", - "integrity": "sha512-Qryc+QG+7BCpvjloFLQrmlSd0RsVRHejRXd78jNO3+oREueCjwG1CCEH1vduw/ZkM1U9TztwIKVIi3+8MJScGg==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.2", - "@smithy/types": "^4.2.0", + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { @@ -31336,14 +30755,12 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.0.tgz", - "integrity": "sha512-Vj1TtwWnuWqdgQI6YTUF5hQ/0jmFiOYsc51CSMgj7QfyO+RF4EnT2HNjoviNlOOmgzgvf3f5yno+EiC4vrnaWQ==", + "version": "4.1.2", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.2", - "@smithy/node-http-handler": "^4.0.4", - "@smithy/types": "^4.2.0", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/types": "^4.1.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", @@ -31356,8 +30773,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -31368,8 +30783,6 @@ }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@smithy/util-utf8": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", @@ -31379,10 +30792,13 @@ "node": ">=18.0.0" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/@tsconfig/node18": { + "version": "18.2.4", + "dev": true, + "license": "MIT" + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@types/node": { - "version": "18.19.83", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.83.tgz", - "integrity": "sha512-D69JeR5SfFS5H6FLbUaS0vE4r1dGhmMBbG4Ed6BNS4wkDK8GZjsdCShT5LCN59vOHEUHnFCY9J4aclXlIphMkA==", + "version": "18.19.80", "dev": true, "license": "MIT", "dependencies": { @@ -31540,7 +30956,7 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/downlevel-dts/node_modules/typescript": { - "version": "5.9.0-dev.20250314", + "version": "5.9.0-dev.20250324", "dev": true, "license": "Apache-2.0", "bin": { @@ -31589,6 +31005,14 @@ "dev": true, "license": "ISC" }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/get-caller-file": { "version": "2.0.5", "dev": true, @@ -31624,6 +31048,17 @@ "node": ">=8" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/hasown": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/inflight": { "version": "1.0.6", "dev": true, @@ -31646,6 +31081,20 @@ "node": ">= 0.10" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/is-core-module": { + "version": "2.16.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "dev": true, @@ -31686,6 +31135,11 @@ "node": ">=0.10.0" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/rechoir": { "version": "0.6.2", "dev": true, @@ -31709,6 +31163,25 @@ "node": ">=0.10.0" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/resolve": { + "version": "1.22.10", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/rimraf": { "version": "3.0.2", "dev": true, @@ -31740,7 +31213,7 @@ "license": "0BSD" }, "src.gen/@amzn/codewhisperer-streaming/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "dev": true, "license": "ISC", "bin": { @@ -31795,7 +31268,13 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/strnum": { - "version": "1.0.5", + "version": "1.1.2", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "license": "MIT" }, "src.gen/@amzn/codewhisperer-streaming/node_modules/supports-color": { @@ -31812,6 +31291,17 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/tree-kill": { "version": "1.2.2", "dev": true, @@ -31824,10 +31314,20 @@ "version": "2.8.1", "license": "0BSD" }, + "src.gen/@amzn/codewhisperer-streaming/node_modules/typescript": { + "version": "5.2.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "src.gen/@amzn/codewhisperer-streaming/node_modules/undici-types": { "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 637fc21649a..f4b31c22d83 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "skippedTestReport": "ts-node ./scripts/skippedTestReport.ts ./packages/amazonq/test/e2e/" }, "devDependencies": { - "@aws-toolkits/telemetry": "^1.0.312", + "@aws-toolkits/telemetry": "^1.0.318", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", diff --git a/packages/amazonq/.changes/1.61.0.json b/packages/amazonq/.changes/1.61.0.json new file mode 100644 index 00000000000..64b0f4da610 --- /dev/null +++ b/packages/amazonq/.changes/1.61.0.json @@ -0,0 +1,14 @@ +{ + "date": "2025-04-22", + "version": "1.61.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Some users not signaled they needed to select a Region Profile to get features working" + }, + { + "type": "bugfix", + "description": "/review: disable auto-review by default" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.62.0.json b/packages/amazonq/.changes/1.62.0.json new file mode 100644 index 00000000000..530f26ccb29 --- /dev/null +++ b/packages/amazonq/.changes/1.62.0.json @@ -0,0 +1,14 @@ +{ + "date": "2025-04-25", + "version": "1.62.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Toast message to warn users if Developer Profile is not selected" + }, + { + "type": "Bug Fix", + "description": "Fix users can not log in successfully with 2+ IDE instnaces open due to throttle error throw by the service" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.63.0.json b/packages/amazonq/.changes/1.63.0.json new file mode 100644 index 00000000000..10020659c5a --- /dev/null +++ b/packages/amazonq/.changes/1.63.0.json @@ -0,0 +1,14 @@ +{ + "date": "2025-05-01", + "version": "1.63.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Q profile selection hangs when a region is blocked" + }, + { + "type": "Feature", + "description": "Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.64.0.json b/packages/amazonq/.changes/1.64.0.json new file mode 100644 index 00000000000..461ad140c01 --- /dev/null +++ b/packages/amazonq/.changes/1.64.0.json @@ -0,0 +1,10 @@ +{ + "date": "2025-05-02", + "version": "1.64.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Enable Amazon Q LSP in AL2 instances" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.65.0.json b/packages/amazonq/.changes/1.65.0.json new file mode 100644 index 00000000000..ec01584f56b --- /dev/null +++ b/packages/amazonq/.changes/1.65.0.json @@ -0,0 +1,14 @@ +{ + "date": "2025-05-05", + "version": "1.65.0", + "entries": [ + { + "type": "Feature", + "description": "Support selecting customizations across all Q profiles with automatic profile switching for enterprise users" + }, + { + "type": "Feature", + "description": "Memorize and autofill users' last Sso login profile" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/1.66.0.json b/packages/amazonq/.changes/1.66.0.json new file mode 100644 index 00000000000..ab4a819b85a --- /dev/null +++ b/packages/amazonq/.changes/1.66.0.json @@ -0,0 +1,14 @@ +{ + "date": "2025-05-09", + "version": "1.66.0", + "entries": [ + { + "type": "Bug Fix", + "description": "Avoid inline completion 'Improperly formed request' errors when file is too large" + }, + { + "type": "Bug Fix", + "description": "Named agent tabs sometimes open with unnecessary input options" + } + ] +} \ No newline at end of file diff --git a/packages/amazonq/.changes/next-release/Bug Fix-9b0e6490-39a8-445f-9d67-9d762de7421c.json b/packages/amazonq/.changes/next-release/Bug Fix-9b0e6490-39a8-445f-9d67-9d762de7421c.json new file mode 100644 index 00000000000..f17516bb8f4 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-9b0e6490-39a8-445f-9d67-9d762de7421c.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Previous and subsequent cells are used as context for completion in a Jupyter notebook" +} diff --git a/packages/amazonq/.vscode/launch.json b/packages/amazonq/.vscode/launch.json index 47a87b7d6f9..b00c5071ce5 100644 --- a/packages/amazonq/.vscode/launch.json +++ b/packages/amazonq/.vscode/launch.json @@ -14,7 +14,8 @@ "env": { "SSMDOCUMENT_LANGUAGESERVER_PORT": "6010", "WEBPACK_DEVELOPER_SERVER": "http://localhost:8080" - // "__AMAZONQLSP_PATH": "${workspaceFolder}/../../../language-servers/app/aws-lsp-codewhisperer-runtimes/out/token-standalone.js", + // Below allows for overrides used during development + // "__AMAZONQLSP_PATH": "${workspaceFolder}/../../../language-servers/app/aws-lsp-codewhisperer-runtimes/out/agent-standalone.js", // "__AMAZONQLSP_UI": "${workspaceFolder}/../../../language-servers/chat-client/build/amazonq-ui.js" }, "envFile": "${workspaceFolder}/.local.env", diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index 64753856884..197aecdfdf6 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,32 @@ +## 1.66.0 2025-05-09 + +- **Bug Fix** Avoid inline completion 'Improperly formed request' errors when file is too large +- **Bug Fix** Named agent tabs sometimes open with unnecessary input options + +## 1.65.0 2025-05-05 + +- **Feature** Support selecting customizations across all Q profiles with automatic profile switching for enterprise users +- **Feature** Memorize and autofill users' last Sso login profile + +## 1.64.0 2025-05-02 + +- **Bug Fix** Enable Amazon Q LSP in AL2 instances + +## 1.63.0 2025-05-01 + +- **Bug Fix** Q profile selection hangs when a region is blocked +- **Feature** Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf + +## 1.62.0 2025-04-25 + +- **Bug Fix** Toast message to warn users if Developer Profile is not selected +- **Bug Fix** Fix users can not log in successfully with 2+ IDE instnaces open due to throttle error throw by the service + +## 1.61.0 2025-04-22 + +- **Bug Fix** Some users not signaled they needed to select a Region Profile to get features working +- **bugfix** /review: disable auto-review by default + ## 1.60.0 2025-04-18 - **Bug Fix** Users might be bound to a customization which they dont have access with the selected profile and it causes service throwing 403 when using inline suggestion and chat features diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index fbcab15a174..c9d6f32e9f6 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -2,7 +2,7 @@ "name": "amazon-q-vscode", "displayName": "Amazon Q", "description": "The most capable generative AI-powered assistant for building, operating, and transforming software, with advanced capabilities for managing data and AI", - "version": "1.61.0-SNAPSHOT", + "version": "1.67.0-SNAPSHOT", "extensionKind": [ "workspace" ], @@ -59,7 +59,6 @@ "watch": "npm run clean && npm run buildScripts && tsc -watch -p ./", "testCompile": "npm run clean && npm run buildScripts && npm run compileOnly", "test": "npm run testCompile && c8 --allowExternal ts-node ../core/scripts/test/launchTest.ts unit dist/test/unit/index.js ../core/dist/src/testFixtures/workspaceFolder", - "testE2E": "npm run testCompile && c8 --allowExternal ts-node ../core/scripts/test/launchTest.ts e2e dist/test/e2e/index.js ../core/dist/src/testFixtures/workspaceFolder", "testWeb": "npm run compileDev && c8 --allowExternal ts-node ../core/scripts/test/launchTest.ts web dist/test/web/testRunnerWebCore.js", "webRun": "npx @vscode/test-web --open-devtools --browserOption=--disable-web-security --waitForDebugger=9222 --extensionDevelopmentPath=. .", "webWatch": "npm run clean && npm run buildScripts && webpack --mode development --watch", @@ -131,6 +130,14 @@ "amazonQChatDisclaimer": { "type": "boolean", "default": false + }, + "amazonQChatPairProgramming": { + "type": "boolean", + "default": false + }, + "amazonQSelectDeveloperProfile": { + "type": "boolean", + "default": false } }, "additionalProperties": false @@ -177,7 +184,25 @@ "amazonQ.workspaceIndexMaxSize": { "type": "number", "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexMaxSize%", - "default": 250, + "default": 2048, + "scope": "application" + }, + "amazonQ.workspaceIndexMaxFileSize": { + "type": "number", + "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexMaxFileSize%", + "default": 10, + "scope": "application" + }, + "amazonQ.workspaceIndexCacheDirPath": { + "type": "string", + "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexCacheDirPath%", + "default": null, + "scope": "application" + }, + "amazonQ.workspaceIndexIgnoreFilePatterns": { + "type": "array", + "markdownDescription": "%AWS.configuration.description.amazonq.workspaceIndexIgnoreFilePatterns%", + "default": [], "scope": "application" }, "amazonQ.ignoredSecurityIssues": { diff --git a/packages/amazonq/src/app/chat/activation.ts b/packages/amazonq/src/app/chat/activation.ts index 2cd7a494a83..19bc5da150d 100644 --- a/packages/amazonq/src/app/chat/activation.ts +++ b/packages/amazonq/src/app/chat/activation.ts @@ -4,54 +4,33 @@ */ import * as vscode from 'vscode' -import { ExtensionContext, window } from 'vscode' +import { ExtensionContext } from 'vscode' import { telemetry } from 'aws-core-vscode/telemetry' import { AuthUtil, CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' import { Commands, placeholder, funcUtil } from 'aws-core-vscode/shared' import * as amazonq from 'aws-core-vscode/amazonq' -import { scanChatAppInit } from '../amazonqScan' export async function activate(context: ExtensionContext) { const appInitContext = amazonq.DefaultAmazonQAppInitContext.instance - - registerApps(appInitContext, context) - - const provider = new amazonq.AmazonQChatViewProvider( - context, - appInitContext.getWebViewToAppsMessagePublishers(), - appInitContext.getAppsToWebViewMessageListener(), - appInitContext.onDidChangeAmazonQVisibility - ) - await amazonq.TryChatCodeLensProvider.register(appInitContext.onDidChangeAmazonQVisibility.event) const setupLsp = funcUtil.debounce(async () => { void amazonq.LspController.instance.trySetupLsp(context, { startUrl: AuthUtil.instance.connection?.startUrl, maxIndexSize: CodeWhispererSettings.instance.getMaxIndexSize(), - isVectorIndexEnabled: CodeWhispererSettings.instance.isLocalIndexEnabled(), + isVectorIndexEnabled: false, }) }, 5000) context.subscriptions.push( - window.registerWebviewViewProvider(amazonq.AmazonQChatViewProvider.viewType, provider, { - webviewOptions: { - retainContextWhenHidden: true, - }, - }), amazonq.focusAmazonQChatWalkthrough.register(), amazonq.walkthroughInlineSuggestionsExample.register(), amazonq.walkthroughSecurityScanExample.register(), amazonq.openAmazonQWalkthrough.register(), amazonq.listCodeWhispererCommandsWalkthrough.register(), - amazonq.tryChatCodeLensCommand.register(), - vscode.workspace.onDidChangeConfiguration(async (configurationChangeEvent) => { - if (configurationChangeEvent.affectsConfiguration('amazonQ.workspaceIndex')) { - if (CodeWhispererSettings.instance.isLocalIndexEnabled()) { - void setupLsp() - } - } - }) + amazonq.focusAmazonQPanel.register(), + amazonq.focusAmazonQPanelKeybinding.register(), + amazonq.tryChatCodeLensCommand.register() ) Commands.register('aws.amazonq.learnMore', () => { @@ -62,15 +41,6 @@ export async function activate(context: ExtensionContext) { void setupAuthNotification() } -function registerApps(appInitContext: amazonq.AmazonQAppInitContext, context: ExtensionContext) { - amazonq.cwChatAppInit(appInitContext) - amazonq.featureDevChatAppInit(appInitContext) - amazonq.gumbyChatAppInit(appInitContext) - amazonq.testChatAppInit(appInitContext) - scanChatAppInit(appInitContext) - amazonq.docChatAppInit(appInitContext) -} - /** * Display a notification to user for Log In. * diff --git a/packages/amazonq/src/app/chat/node/activateAgents.ts b/packages/amazonq/src/app/chat/node/activateAgents.ts new file mode 100644 index 00000000000..954f2892eda --- /dev/null +++ b/packages/amazonq/src/app/chat/node/activateAgents.ts @@ -0,0 +1,19 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as amazonqNode from 'aws-core-vscode/amazonq/node' +import { scanChatAppInit } from '../../amazonqScan' +import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' + +export function activateAgents() { + const appInitContext = DefaultAmazonQAppInitContext.instance + + amazonqNode.cwChatAppInit(appInitContext) + amazonqNode.featureDevChatAppInit(appInitContext) + amazonqNode.gumbyChatAppInit(appInitContext) + amazonqNode.testChatAppInit(appInitContext) + amazonqNode.docChatAppInit(appInitContext) + scanChatAppInit(appInitContext) +} diff --git a/packages/amazonq/src/extension.ts b/packages/amazonq/src/extension.ts index 8c469b737e0..5cef3994b7a 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CredentialsStore, LoginManager, authUtils, initializeAuth } from 'aws-core-vscode/auth' +import { authUtils, CredentialsStore, LoginManager, initializeAuth } from 'aws-core-vscode/auth' import { activate as activateCodeWhisperer, shutdown as shutdownCodeWhisperer } from 'aws-core-vscode/codewhisperer' import { makeEndpointsProvider, registerGenericCommands } from 'aws-core-vscode' import { CommonAuthWebview } from 'aws-core-vscode/login' @@ -44,7 +44,6 @@ import { registerCommands } from './commands' import { focusAmazonQPanel } from 'aws-core-vscode/codewhispererChat' import { activate as activateAmazonqLsp } from './lsp/activation' import { activate as activateInlineCompletion } from './app/inline/activation' -import { isAmazonInternalOs } from 'aws-core-vscode/shared' export const amazonQContextPrefix = 'amazonq' @@ -120,11 +119,11 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is extensionContext: context, } + // Auth is dependent on LSP, needs to be activated before CW and Inline await activateAmazonqLsp(context) // This contains every lsp agnostic things (auth, security scan, code scan) await activateCodeWhisperer(extContext as ExtContext) - if (!Experiments.instance.get('amazonqLSPInline', false)) { await activateInlineCompletion() } diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index f11a3e14f1d..2bcf5c152ce 100644 --- a/packages/amazonq/src/extensionNode.ts +++ b/packages/amazonq/src/extensionNode.ts @@ -5,8 +5,8 @@ import * as vscode from 'vscode' import { activateAmazonQCommon, amazonQContextPrefix, deactivateCommon } from './extension' -import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' -import { activate as activateQGumby } from 'aws-core-vscode/amazonqGumby' +import { DefaultAmazonQAppInitContext, AmazonQChatViewProvider } from 'aws-core-vscode/amazonq' +import { activate as activateTransformationHub } from 'aws-core-vscode/amazonqGumby' import { ExtContext, globals, CrashMonitoring, getLogger, isSageMaker, Experiments } from 'aws-core-vscode/shared' import { filetypes, SchemaService } from 'aws-core-vscode/sharedNode' import { updateDevMode } from 'aws-core-vscode/dev' @@ -19,10 +19,10 @@ import api from './api' import { activate as activateCWChat } from './app/chat/activation' import { activate as activateInlineChat } from './inlineChat/activation' import { beta } from 'aws-core-vscode/dev' -import * as amazonq from 'aws-core-vscode/amazonq' import { activate as activateNotifications, NotificationsController } from 'aws-core-vscode/notifications' import { AuthUtil } from 'aws-core-vscode/codewhisperer' -import { telemetry, AuthUserState, AuthStatus } from 'aws-core-vscode/telemetry' +import { telemetry, AuthUserState } from 'aws-core-vscode/telemetry' +import { activateAgents } from './app/chat/node/activateAgents' export async function activate(context: vscode.ExtensionContext) { // IMPORTANT: No other code should be added to this function. Place it in one of the following 2 functions where appropriate. @@ -45,14 +45,28 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) { extensionContext: context, } - if (!Experiments.instance.get('amazonqChatLSP', false)) { + if (!Experiments.instance.get('amazonqChatLSP', true)) { + const appInitContext = DefaultAmazonQAppInitContext.instance + const provider = new AmazonQChatViewProvider( + context, + appInitContext.getWebViewToAppsMessagePublishers(), + appInitContext.getAppsToWebViewMessageListener(), + appInitContext.onDidChangeAmazonQVisibility + ) + context.subscriptions.push( + vscode.window.registerWebviewViewProvider(AmazonQChatViewProvider.viewType, provider, { + webviewOptions: { + retainContextWhenHidden: true, + }, + }) + ) + // this is registered inside of lsp/chat/activation.ts when the chat experiment is enabled await activateCWChat(context) - await activateQGumby(extContext as ExtContext) } + activateAgents() + await activateTransformationHub(extContext as ExtContext) activateInlineChat(context) - context.subscriptions.push(amazonq.focusAmazonQPanel.register(), amazonq.focusAmazonQPanelKeybinding.register()) - const authProvider = new CommonAuthViewProvider( context, amazonQContextPrefix, diff --git a/packages/amazonq/src/lsp/activation.ts b/packages/amazonq/src/lsp/activation.ts index 193d306f5f0..fe1e2f15ddc 100644 --- a/packages/amazonq/src/lsp/activation.ts +++ b/packages/amazonq/src/lsp/activation.ts @@ -4,19 +4,17 @@ */ import vscode from 'vscode' -import { clientId, encryptionKey, startLanguageServer } from './client' +import { startLanguageServer } from './client' import { AmazonQLspInstaller } from './lspInstaller' import { lspSetupStage, ToolkitError, messages } from 'aws-core-vscode/shared' import { AuthUtil } from 'aws-core-vscode/codewhisperer' -import { auth2 } from 'aws-core-vscode/auth' export async function activate(ctx: vscode.ExtensionContext) { try { - const client = await lspSetupStage('all', async () => { + await lspSetupStage('all', async () => { const installResult = await new AmazonQLspInstaller().resolve() return await lspSetupStage('launch', () => startLanguageServer(ctx, installResult.resourcePaths)) }) - AuthUtil.create(new auth2.LanguageClientAuth(client, clientId, encryptionKey)) await AuthUtil.instance.restore() } catch (err) { const e = err as ToolkitError diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index 406b753716f..3a36377b9b5 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -8,12 +8,32 @@ import { LanguageClient } from 'vscode-languageclient' import { AmazonQChatViewProvider } from './webviewProvider' import { registerCommands } from './commands' import { registerLanguageServerEventListener, registerMessageListeners } from './messages' -import { globals } from 'aws-core-vscode/shared' +import { Commands, getLogger, globals, undefinedIfEmpty } from 'aws-core-vscode/shared' +import { activate as registerLegacyChatListeners } from '../../app/chat/activation' +import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' +import { AuthUtil, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' +import { + DidChangeConfigurationNotification, + updateConfigurationRequestType, +} from '@aws/language-server-runtimes/protocol' + +export async function activate(languageClient: LanguageClient, encryptionKey: Buffer, mynahUIPath: string) { + const disposables = globals.context.subscriptions + + // Make sure we've sent an auth profile to the language server before even initializing the UI + await pushConfigUpdate(languageClient, { + type: 'profile', + profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, + }) + // We need to push the cached customization on startup explicitly + await pushConfigUpdate(languageClient, { + type: 'customization', + customization: getSelectedCustomization(), + }) -export function activate(languageClient: LanguageClient, encryptionKey: Buffer, mynahUIPath: string) { const provider = new AmazonQChatViewProvider(mynahUIPath) - globals.context.subscriptions.push( + disposables.push( window.registerWebviewViewProvider(AmazonQChatViewProvider.viewType, provider, { webviewOptions: { retainContextWhenHidden: true, @@ -29,6 +49,95 @@ export function activate(languageClient: LanguageClient, encryptionKey: Buffer, registerLanguageServerEventListener(languageClient, provider) provider.onDidResolveWebview(() => { + const disposable = DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessageListener().onMessage((msg) => { + /** + * codewhispers app handler is still registered because the activation flow hasn't been refactored. + * We need to explicitly deny events like restoreTabMessage, otherwise they will be forwarded to the frontend + * + */ + if (msg.sender === 'CWChat' && ['restoreTabMessage', 'contextCommandData'].includes(msg.type)) { + return + } + provider.webview?.postMessage(msg).then(undefined, (e) => { + getLogger().error('webView.postMessage failed: %s', (e as Error).message) + }) + }) + + if (provider.webviewView) { + disposables.push( + provider.webviewView.onDidDispose(() => { + disposable.dispose() + }) + ) + } + registerMessageListeners(languageClient, provider, encryptionKey) }) + + // register event listeners from the legacy agent flow + await registerLegacyChatListeners(globals.context) + + disposables.push( + AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(async () => { + void pushConfigUpdate(languageClient, { + type: 'profile', + profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, + }) + await provider.refreshWebview() + }), + Commands.register('aws.amazonq.updateCustomizations', () => { + void pushConfigUpdate(languageClient, { + type: 'customization', + customization: undefinedIfEmpty(getSelectedCustomization().arn), + }) + }), + globals.logOutputChannel.onDidChangeLogLevel((logLevel) => { + getLogger('amazonqLsp').info(`Local log level changed to ${logLevel}, notifying LSP`) + void pushConfigUpdate(languageClient, { + type: 'logLevel', + }) + }) + ) +} + +/** + * Push a config value to the language server, effectively updating it with the + * latest configuration from the client. + * + * The issue is we need to push certain configs to different places, since there are + * different handlers for specific configs. So this determines the correct place to + * push the given config. + */ +async function pushConfigUpdate(client: LanguageClient, config: QConfigs) { + switch (config.type) { + case 'profile': + await client.sendRequest(updateConfigurationRequestType.method, { + section: 'aws.q', + settings: { profileArn: config.profileArn }, + }) + break + case 'customization': + client.sendNotification(DidChangeConfigurationNotification.type.method, { + section: 'aws.q', + settings: { customization: config.customization }, + }) + break + case 'logLevel': + client.sendNotification(DidChangeConfigurationNotification.type.method, { + section: 'aws.logLevel', + }) + break + } +} +type ProfileConfig = { + type: 'profile' + profileArn: string | undefined +} +type CustomizationConfig = { + type: 'customization' + customization: string | undefined +} +type LogLevelConfig = { + type: 'logLevel' } +type QConfigs = ProfileConfig | CustomizationConfig | LogLevelConfig diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index dd495d1bfbf..74c63592a4f 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -6,13 +6,66 @@ import { Commands, globals } from 'aws-core-vscode/shared' import { window } from 'vscode' import { AmazonQChatViewProvider } from './webviewProvider' +import { CodeScanIssue } from 'aws-core-vscode/codewhisperer' +import { EditorContextExtractor } from 'aws-core-vscode/codewhispererChat' +import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' +/** + * TODO: Re-enable these once we can figure out which path they're going to live in + * In hybrid chat mode they were being registered twice causing a registration error + */ export function registerCommands(provider: AmazonQChatViewProvider) { globals.context.subscriptions.push( registerGenericCommand('aws.amazonq.explainCode', 'Explain', provider), registerGenericCommand('aws.amazonq.refactorCode', 'Refactor', provider), registerGenericCommand('aws.amazonq.fixCode', 'Fix', provider), registerGenericCommand('aws.amazonq.optimizeCode', 'Optimize', provider), + Commands.register('aws.amazonq.generateUnitTests', async () => { + DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({ + sender: 'testChat', + command: 'test', + type: 'chatMessage', + }) + }), + Commands.register('aws.amazonq.explainIssue', async (issue: CodeScanIssue) => { + void focusAmazonQPanel().then(async () => { + const editorContextExtractor = new EditorContextExtractor() + const extractedContext = await editorContextExtractor.extractContextForTrigger('ContextMenu') + const selectedCode = + extractedContext?.activeFileContext?.fileText + ?.split('\n') + .slice(issue.startLine, issue.endLine) + .join('\n') ?? '' + + // The message that gets sent to the UI + const uiMessage = [ + 'Explain the ', + issue.title, + ' issue in the following code:', + '\n```\n', + selectedCode, + '\n```', + ].join('') + + // The message that gets sent to the backend + const contextMessage = `Explain the issue "${issue.title}" (${JSON.stringify( + issue + )}) and generate code demonstrating the fix` + + void provider.webview?.postMessage({ + command: 'sendToPrompt', + params: { + selection: '', + triggerType: 'contextMenu', + prompt: { + prompt: uiMessage, // what gets sent to the user + escapedPrompt: contextMessage, // what gets sent to the backend + }, + autoSubmit: true, + }, + }) + }) + }), Commands.register('aws.amazonq.sendToPrompt', (data) => { const triggerType = getCommandTriggerType(data) const selection = getSelectedText() diff --git a/packages/amazonq/src/lsp/chat/error.ts b/packages/amazonq/src/lsp/chat/error.ts new file mode 100644 index 00000000000..fc2e0211b0f --- /dev/null +++ b/packages/amazonq/src/lsp/chat/error.ts @@ -0,0 +1,23 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { ChatResult } from '@aws/language-server-runtimes/protocol' +import { ResponseError } from '@aws/language-server-runtimes/protocol' +/** + * Perform a sanity check that the error we got from the LSP can be safely cast to the expected type. + * @param error + * @returns + */ +export function isValidResponseError(error: unknown): error is ResponseError & { data: ChatResult } { + return ( + typeof error === 'object' && + error !== null && + 'code' in error && + typeof error.code === 'number' && + 'message' in error && + typeof error.message === 'string' && + 'data' in error && + error.data !== undefined + ) +} diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 7d557b5f338..fd901ee7dc9 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -12,6 +12,10 @@ import { AuthFollowUpType, DISCLAIMER_ACKNOWLEDGED, UiMessageResultParams, + CHAT_PROMPT_OPTION_ACKNOWLEDGED, + ChatPromptOptionAcknowledgedMessage, + STOP_CHAT_RESPONSE, + StopChatResponseMessage, } from '@aws/chat-client-ui-types' import { ChatResult, @@ -32,19 +36,35 @@ import { ShowSaveFileDialogParams, LSPErrorCodes, tabBarActionRequestType, - ShowDocumentParams, - ShowDocumentResult, - ShowDocumentRequest, contextCommandsNotificationType, ContextCommandParams, + openFileDiffNotificationType, + OpenFileDiffParams, + LINK_CLICK_NOTIFICATION_METHOD, + LinkClickParams, + INFO_LINK_CLICK_NOTIFICATION_METHOD, + buttonClickRequestType, + ButtonClickResult, + CancellationTokenSource, + chatUpdateNotificationType, + ChatUpdateParams, } from '@aws/language-server-runtimes/protocol' import { v4 as uuidv4 } from 'uuid' import * as vscode from 'vscode' import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vscode-languageclient' import * as jose from 'jose' import { AmazonQChatViewProvider } from './webviewProvider' -import { AuthUtil } from 'aws-core-vscode/codewhisperer' -import { AmazonQPromptSettings, messages } from 'aws-core-vscode/shared' +import { AuthUtil, ReferenceLogViewProvider } from 'aws-core-vscode/codewhisperer' +import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' +import { + DefaultAmazonQAppInitContext, + messageDispatcher, + EditorContentController, + ViewDiffMessage, + referenceLogText, +} from 'aws-core-vscode/amazonq' +import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry' +import { isValidResponseError } from './error' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -54,9 +74,10 @@ export function registerLanguageServerEventListener(languageClient: LanguageClie const chatOptions = languageClient.initializeResult?.awsServerCapabilities?.chatOptions - // Enable the history/export feature flags - chatOptions.history = true - chatOptions.export = true + // overide the quick action commands provided by flare server initialization, which doesn't provide the group header + if (chatOptions?.quickActions?.quickActionsCommandGroups?.[0]) { + chatOptions.quickActions.quickActionsCommandGroups[0].groupName = 'Quick Actions' + } provider.onDidResolveWebview(() => { void provider.webview?.postMessage({ @@ -65,19 +86,50 @@ export function registerLanguageServerEventListener(languageClient: LanguageClie }) }) + // This passes through metric data from LSP events to Toolkit telemetry with all fields from the LSP server languageClient.onTelemetry((e) => { - languageClient.info(`[VSCode Client] Received telemetry event from server ${JSON.stringify(e)}`) + const telemetryName: string = e.name + + if (telemetryName in telemetry) { + languageClient.info(`[Telemetry] Emitting ${telemetryName} telemetry: ${JSON.stringify(e.data)}`) + telemetry[telemetryName as keyof TelemetryBase].emit(e.data) + } }) } +function getCursorState(selection: readonly vscode.Selection[]) { + return selection.map((s) => ({ + range: { + start: { + line: s.start.line, + character: s.start.character, + }, + end: { + line: s.end.line, + character: s.end.character, + }, + }, + })) +} + export function registerMessageListeners( languageClient: LanguageClient, provider: AmazonQChatViewProvider, encryptionKey: Buffer ) { + const chatStreamTokens = new Map() // tab id -> token provider.webview?.onDidReceiveMessage(async (message) => { languageClient.info(`[VSCode Client] Received ${JSON.stringify(message)} from chat`) + if ((message.tabType && message.tabType !== 'cwc') || messageDispatcher.isLegacyEvent(message.command)) { + // handle the mynah ui -> agent legacy flow + messageDispatcher.handleWebviewEvent( + message, + DefaultAmazonQAppInitContext.instance.getWebViewToAppsMessagePublishers() + ) + return + } + const webview = provider.webview switch (message.command) { case COPY_TO_CLIPBOARD: @@ -132,35 +184,100 @@ export function registerMessageListeners( break } case DISCLAIMER_ACKNOWLEDGED: { - void AmazonQPromptSettings.instance.disablePrompt('amazonQChatDisclaimer') + void AmazonQPromptSettings.instance.update('amazonQChatDisclaimer', true) + break + } + case CHAT_PROMPT_OPTION_ACKNOWLEDGED: { + const acknowledgedMessage = message as ChatPromptOptionAcknowledgedMessage + switch (acknowledgedMessage.params.messageId) { + case 'programmerModeCardId': { + void AmazonQPromptSettings.instance.disablePrompt('amazonQChatPairProgramming') + } + } + break + } + case INFO_LINK_CLICK_NOTIFICATION_METHOD: + case LINK_CLICK_NOTIFICATION_METHOD: { + const linkParams = message.params as LinkClickParams + void openUrl(vscode.Uri.parse(linkParams.link)) + break + } + case STOP_CHAT_RESPONSE: { + const tabId = (message as StopChatResponseMessage).params.tabId + const token = chatStreamTokens.get(tabId) + token?.cancel() + token?.dispose() + chatStreamTokens.delete(tabId) break } case chatRequestType.method: { + const chatParams: ChatParams = { ...message.params } const partialResultToken = uuidv4() - const chatDisposable = languageClient.onProgress(chatRequestType, partialResultToken, (partialResult) => - handlePartialResult(partialResult, encryptionKey, provider, message.params.tabId) + let lastPartialResult: ChatResult | undefined + const cancellationToken = new CancellationTokenSource() + chatStreamTokens.set(chatParams.tabId, cancellationToken) + + const chatDisposable = languageClient.onProgress( + chatRequestType, + partialResultToken, + (partialResult) => { + // Store the latest partial result + if (typeof partialResult === 'string' && encryptionKey) { + void decodeRequest(partialResult, encryptionKey).then( + (decoded) => (lastPartialResult = decoded) + ) + } else { + lastPartialResult = partialResult as ChatResult + } + + void handlePartialResult(partialResult, encryptionKey, provider, chatParams.tabId) + } ) const editor = vscode.window.activeTextEditor || vscode.window.visibleTextEditors.find((editor) => editor.document.languageId !== 'Log') if (editor) { - message.params.cursorPosition = [editor.selection.active] - message.params.textDocument = { uri: editor.document.uri.toString() } + chatParams.cursorState = getCursorState(editor.selections) + chatParams.textDocument = { uri: editor.document.uri.toString() } } - const chatRequest = await encryptRequest(message.params, encryptionKey) - const chatResult = (await languageClient.sendRequest(chatRequestType.method, { - ...chatRequest, - partialResultToken, - })) as string | ChatResult - void handleCompleteResult( - chatResult, - encryptionKey, - provider, - message.params.tabId, - chatDisposable - ) + const chatRequest = await encryptRequest(chatParams, encryptionKey) + try { + const chatResult = await languageClient.sendRequest( + chatRequestType.method, + { + ...chatRequest, + partialResultToken, + }, + cancellationToken.token + ) + await handleCompleteResult( + chatResult, + encryptionKey, + provider, + chatParams.tabId, + chatDisposable + ) + } catch (e) { + const errorMsg = `Error occurred during chat request: ${e}` + languageClient.info(errorMsg) + languageClient.info( + `Last result from langauge server: ${JSON.stringify(lastPartialResult, undefined, 2)}` + ) + if (!isValidResponseError(e)) { + throw e + } + await handleCompleteResult( + e.data, + encryptionKey, + provider, + chatParams.tabId, + chatDisposable + ) + } finally { + chatStreamTokens.delete(chatParams.tabId) + } break } case quickActionRequestType.method: { @@ -201,6 +318,18 @@ export function registerMessageListeners( languageClient.sendNotification(followUpClickNotificationType.method, message.params) } break + case buttonClickRequestType.method: { + const buttonResult = await languageClient.sendRequest( + buttonClickRequestType.method, + message.params + ) + if (!buttonResult.success) { + languageClient.error( + `[VSCode Client] Failed to execute action associated with button with reason: ${buttonResult.failureReason}` + ) + } + break + } default: if (isServerEvent(message.command)) { languageClient.sendNotification(message.command, message.params) @@ -292,29 +421,47 @@ export function registerMessageListeners( } }) - languageClient.onRequest( - ShowDocumentRequest.method, - async (params: ShowDocumentParams): Promise> => { - try { - const uri = vscode.Uri.parse(params.uri) - const doc = await vscode.workspace.openTextDocument(uri) - await vscode.window.showTextDocument(doc, { preview: false }) - return params - } catch (e) { - return new ResponseError( - LSPErrorCodes.RequestFailed, - `Failed to open document: ${(e as Error).message}` - ) - } - } - ) - languageClient.onNotification(contextCommandsNotificationType.method, (params: ContextCommandParams) => { void provider.webview?.postMessage({ command: contextCommandsNotificationType.method, params: params, }) }) + + languageClient.onNotification(openFileDiffNotificationType.method, async (params: OpenFileDiffParams) => { + const ecc = new EditorContentController() + const uri = params.originalFileUri + const doc = await vscode.workspace.openTextDocument(uri) + const entireDocumentSelection = new vscode.Selection( + new vscode.Position(0, 0), + new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length) + ) + const viewDiffMessage: ViewDiffMessage = { + context: { + activeFileContext: { + filePath: params.originalFileUri, + fileText: params.originalFileContent ?? '', + fileLanguage: undefined, + matchPolicy: undefined, + }, + focusAreaContext: { + selectionInsideExtendedCodeBlock: entireDocumentSelection, + codeBlock: '', + extendedCodeBlock: '', + names: undefined, + }, + }, + code: params.fileContent ?? '', + } + await ecc.viewDiff(viewDiffMessage, amazonQDiffScheme) + }) + + languageClient.onNotification(chatUpdateNotificationType.method, (params: ChatUpdateParams) => { + void provider.webview?.postMessage({ + command: chatUpdateNotificationType.method, + params: params, + }) + }) } function isServerEvent(command: string) { @@ -358,7 +505,7 @@ async function handlePartialResult( ? await decodeRequest(partialResult, encryptionKey) : (partialResult as T) - if (decryptedMessage.body) { + if (decryptedMessage.body !== undefined) { void provider.webview?.postMessage({ command: chatRequestType.method, params: decryptedMessage, @@ -372,7 +519,7 @@ async function handlePartialResult( * Decodes the final chat responses from the language server before sending it to mynah UI. * Once this is called the answer response is finished */ -async function handleCompleteResult( +async function handleCompleteResult( result: string | T, encryptionKey: Buffer | undefined, provider: AmazonQChatViewProvider, @@ -380,13 +527,17 @@ async function handleCompleteResult( disposable: Disposable ) { const decryptedMessage = - typeof result === 'string' && encryptionKey ? await decodeRequest(result, encryptionKey) : result - + typeof result === 'string' && encryptionKey ? await decodeRequest(result, encryptionKey) : (result as T) void provider.webview?.postMessage({ command: chatRequestType.method, params: decryptedMessage, tabId: tabId, }) + + // only add the reference log once the request is complete, otherwise we will get duplicate log items + for (const ref of decryptedMessage.codeReference ?? []) { + ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref)) + } disposable.dispose() } diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index ee73a599867..3f9d273ab96 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -6,40 +6,34 @@ import { EventEmitter, CancellationToken, - Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, Uri, + Webview, } from 'vscode' -import { QuickActionCommandGroup } from '@aws/mynah-ui' import * as path from 'path' -import { AmazonQPromptSettings, LanguageServerResolver } from 'aws-core-vscode/shared' +import { + globals, + isSageMaker, + AmazonQPromptSettings, + LanguageServerResolver, + amazonqMark, +} from 'aws-core-vscode/shared' +import { AuthUtil, RegionProfile } from 'aws-core-vscode/codewhisperer' +import { featureConfig } from 'aws-core-vscode/amazonq' +import { getAmazonQLspConfig } from '../config' export class AmazonQChatViewProvider implements WebviewViewProvider { public static readonly viewType = 'aws.amazonq.AmazonQChatView' private readonly onDidResolveWebviewEmitter = new EventEmitter() public readonly onDidResolveWebview = this.onDidResolveWebviewEmitter.event - webview: Webview | undefined - - private readonly quickActionCommands: QuickActionCommandGroup[] = [ - { - groupName: 'Quick Actions', - commands: [ - { - command: '/help', - icon: 'help', - description: 'Learn more about Amazon Q', - }, - { - command: '/clear', - icon: 'trash', - description: 'Clear this session', - }, - ], - }, - ] + webviewView?: WebviewView + webview?: Webview + + connectorAdapterPath?: string + uiPath?: string constructor(private readonly mynahUIPath: string) {} @@ -48,28 +42,79 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { context: WebviewViewResolveContext, _token: CancellationToken ) { - this.webview = webviewView.webview + const lspDir = Uri.file(LanguageServerResolver.defaultDir()) + const dist = Uri.joinPath(globals.context.extensionUri, 'dist') + + const resourcesRoots = [lspDir, dist] + + /** + * if the mynah chat client is defined, then make sure to add it to the resource roots, otherwise + * it will 401 when trying to load + */ + const mynahUIPath = getAmazonQLspConfig().ui + if (process.env.WEBPACK_DEVELOPER_SERVER && mynahUIPath) { + const dir = path.dirname(mynahUIPath) + resourcesRoots.push(Uri.file(dir)) + } - const lspDir = Uri.parse(LanguageServerResolver.defaultDir()) webviewView.webview.options = { enableScripts: true, enableCommandUris: true, - localResourceRoots: [lspDir, Uri.parse(path.dirname(this.mynahUIPath))], + localResourceRoots: resourcesRoots, } - const uiPath = webviewView.webview.asWebviewUri(Uri.parse(this.mynahUIPath)).toString() - webviewView.webview.html = await this.getWebviewContent(uiPath) + const source = 'vue/src/amazonq/webview/ui/amazonq-ui-connector-adapter.js' // Sent to dist/vue folder in webpack. + const serverHostname = process.env.WEBPACK_DEVELOPER_SERVER + + this.connectorAdapterPath = + serverHostname !== undefined + ? `${serverHostname}/${source}` + : webviewView.webview.asWebviewUri(Uri.joinPath(dist, source)).toString() + this.uiPath = webviewView.webview.asWebviewUri(Uri.file(this.mynahUIPath)).toString() + + webviewView.webview.html = await this.getWebviewContent() + + this.webviewView = webviewView + this.webview = this.webviewView.webview this.onDidResolveWebviewEmitter.fire() + performance.mark(amazonqMark.open) } - private async getWebviewContent(mynahUIPath: string) { + private async getWebviewContent() { + const featureConfigData = await featureConfig.getFeatureConfigs() + + const isSM = isSageMaker('SMAI') + const isSMUS = isSageMaker('SMUS') + const disabledCommands = isSM ? `['/dev', '/transform', '/test', '/review', '/doc']` : '[]' const disclaimerAcknowledged = !AmazonQPromptSettings.instance.isPromptEnabled('amazonQChatDisclaimer') + const pairProgrammingAcknowledged = + !AmazonQPromptSettings.instance.isPromptEnabled('amazonQChatPairProgramming') + const welcomeCount = globals.globalState.tryGet('aws.amazonq.welcomeChatShowCount', Number, 0) + + // only show profile card when the two conditions + // 1. profile count >= 2 + // 2. not default (fallback) which has empty arn + let regionProfile: RegionProfile | undefined = AuthUtil.instance.regionProfileManager.activeRegionProfile + if (AuthUtil.instance.regionProfileManager.profiles.length === 1) { + regionProfile = undefined + } + + const regionProfileString: string = JSON.stringify(regionProfile) + + const entrypoint = process.env.WEBPACK_DEVELOPER_SERVER + ? 'http://localhost:8080' + : 'https://file+.vscode-resource.vscode-cdn.net' + + const contentPolicy = `default-src ${entrypoint} data: blob: 'unsafe-inline'; + script-src ${entrypoint} filesystem: file: vscode-resource: https: ws: wss: 'unsafe-inline';` + return ` + Chat - + + ` } + + async refreshWebview() { + if (this.webview) { + // post a message to the webview telling it to reload + void this.webview?.postMessage({ + command: 'reload', + }) + } + } } diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index deadadcdc82..24ccafcc9b3 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -7,11 +7,9 @@ import vscode, { env, version } from 'vscode' import * as nls from 'vscode-nls' import * as crypto from 'crypto' import * as jose from 'jose' -import { LanguageClient, LanguageClientOptions, RequestType } from 'vscode-languageclient' +import { LanguageClient, LanguageClientOptions, RequestType, State } from 'vscode-languageclient' import { InlineCompletionManager } from '../app/inline/completion' -import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { - ConnectionMetadata, CreateFilesParams, DeleteFilesParams, DidChangeWorkspaceFoldersParams, @@ -25,26 +23,39 @@ import { GetSsoTokenProgressToken, GetSsoTokenProgressType, MessageActionItem, - ShowDocumentParams, - ShowDocumentRequest, - ShowDocumentResult, ShowMessageRequest, ShowMessageRequestParams, + ConnectionMetadata, + ShowDocumentRequest, + ShowDocumentParams, + ShowDocumentResult, + ResponseError, + LSPErrorCodes, } from '@aws/language-server-runtimes/protocol' +import { AuthUtil, CodeWhispererSettings, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' import { Settings, - oidcClientName, createServerOptions, globals, Experiments, Commands, - openUrl, validateNodeExe, getLogger, + undefinedIfEmpty, + getOptOutPreference, + isAmazonInternalOs, + fs, + oidcClientName, + openUrl, + getClientId, + extensionVersion, } from 'aws-core-vscode/shared' -import { activate } from './chat/activation' +import { processUtils } from 'aws-core-vscode/shared' +import { activate as activateChat } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' import { auth2 } from 'aws-core-vscode/auth' +import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config' +import { telemetry } from 'aws-core-vscode/telemetry' const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') @@ -53,6 +64,10 @@ export const clientId = 'amazonq' export const clientName = oidcClientName() export const encryptionKey = crypto.randomBytes(32) +export async function hasGlibcPatch(): Promise { + return await fs.exists('/opt/vsc-sysroot/lib64/ld-linux-x86-64.so.2') +} + export async function startLanguageServer( extensionContext: vscode.ExtensionContext, resourcePaths: AmazonQResourcePaths @@ -68,41 +83,77 @@ export async function startLanguageServer( '--pre-init-encryption', '--set-credentials-encryption-key', ] + + const documentSelector = [{ scheme: 'file', language: '*' }] + const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`) + let executable: string[] = [] + // apply the GLIBC 2.28 path to node js runtime binary + if (isAmazonInternalOs() && (await hasGlibcPatch())) { + executable = [ + '/opt/vsc-sysroot/lib64/ld-linux-x86-64.so.2', + '--library-path', + '/opt/vsc-sysroot/lib64', + resourcePaths.node, + ] + getLogger('amazonqLsp').info(`Patched node runtime with GLIBC to ${executable}`) + } else { + executable = [resourcePaths.node] + } + + const memoryWarnThreshold = 1024 * processUtils.oneMB const serverOptions = createServerOptions({ encryptionKey, - executable: resourcePaths.node, + executable: executable, serverModule, execArgv: argv, + warnThresholds: { memory: memoryWarnThreshold }, }) - const documentSelector = [{ scheme: 'file', language: '*' }] - const traceServerEnabled = Settings.instance.isSet(`${clientId}.trace.server`) - - await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger) + await validateNodeExe(executable, resourcePaths.lsp, argv, logger) // Options to control the language client const clientOptions: LanguageClientOptions = { // Register the server for json documents documentSelector, + middleware: { + workspace: { + /** + * Convert VSCode settings format to be compatible with flare's configs + */ + configuration: async (params, token, next) => { + const config = await next(params, token) + const section = params.items[0].section + if (!isValidConfigSection(section)) { + return config + } + return getConfigSection(section) + }, + }, + }, initializationOptions: { aws: { clientInfo: { name: env.appName, version: version, extension: { - name: oidcClientName(), - version: '0.0.1', + name: 'AmazonQ-For-VSCode', + version: extensionVersion, }, - clientId: crypto.randomUUID(), + clientId: getClientId(globals.globalState), }, awsClientCapabilities: { - window: { - notifications: true, - }, q: { developerProfiles: true, }, + window: { + notifications: true, + showSaveFileDialog: true, + }, + }, + contextConfiguration: { + workspaceIdentifier: extensionContext.storageUri?.path, }, + logLevel: toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel), }, credentials: { providesBearerToken: true, @@ -127,6 +178,7 @@ export async function startLanguageServer( toDispose.push(disposable) await client.onReady() + AuthUtil.create(new auth2.LanguageClientAuth(client, clientId, encryptionKey)) // Request handler for when the server wants to know about the clients auth connnection. Must be registered before the initial auth init call client.onRequest(auth2.notificationTypes.getConnectionMetadata.method, () => { @@ -137,15 +189,6 @@ export async function startLanguageServer( } }) - client.onRequest(ShowDocumentRequest.method, async (params: ShowDocumentParams) => { - try { - return { success: await openUrl(vscode.Uri.parse(params.uri), lspName) } - } catch (err: any) { - getLogger().error(`Failed to open document for LSP: ${lspName}, error: %s`, err) - return { success: false } - } - }) - client.onRequest( ShowMessageRequest.method, async (params: ShowMessageRequestParams) => { @@ -155,6 +198,47 @@ export async function startLanguageServer( } ) + client.onRequest( + ShowDocumentRequest.method, + async (params: ShowDocumentParams): Promise> => { + const uri = vscode.Uri.parse(params.uri) + getLogger().info(`Processing ShowDocumentRequest for URI scheme: ${uri.scheme}`) + try { + if (uri.scheme.startsWith('http')) { + getLogger().info('Opening URL...') + await openUrl(vscode.Uri.parse(params.uri)) + } else { + getLogger().info('Opening text document...') + const doc = await vscode.workspace.openTextDocument(uri) + await vscode.window.showTextDocument(doc, { preview: false }) + } + return params + } catch (e) { + return new ResponseError( + LSPErrorCodes.RequestFailed, + `Failed to process ShowDocumentRequest: ${(e as Error).message}` + ) + } + } + ) + + const sendProfileToLsp = async () => { + try { + const result = await client.sendRequest(updateConfigurationRequestType.method, { + section: 'aws.q', + settings: { + profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, + }, + }) + client.info( + `Client: Updated Amazon Q Profile ${AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn} to Amazon Q LSP`, + result + ) + } catch (err) { + client.error('Error when setting Q Developer Profile to Amazon Q LSP', err) + } + } + let promise: Promise | undefined let resolver: () => void = () => {} client.onProgress(GetSsoTokenProgressType, GetSsoTokenProgressToken, async (partialResult: GetSsoTokenProgress) => { @@ -174,23 +258,6 @@ export async function startLanguageServer( return } - const sendProfileToLsp = async () => { - try { - const result = await client.sendRequest(updateConfigurationRequestType.method, { - section: 'aws.q', - settings: { - profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, - }, - }) - client.info( - `Client: Updated Amazon Q Profile ${AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn} to Amazon Q LSP`, - result - ) - } catch (err) { - client.error('Error when setting Q Developer Profile to Amazon Q LSP', err) - } - } - // send profile to lsp once. void sendProfileToLsp() @@ -214,75 +281,138 @@ export async function startLanguageServer( Commands.register({ id: 'aws.amazonq.invokeInlineCompletion', autoconnect: true }, async () => { await vscode.commands.executeCommand('editor.action.inlineSuggest.trigger') }), - - AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(sendProfileToLsp), - vscode.commands.registerCommand('aws.amazonq.getWorkspaceId', async () => { - const requestType = new RequestType( - 'aws/getConfigurationFromServer' - ) - const workspaceIdResp = await client.sendRequest(requestType.method, { - section: 'aws.q.workspaceContext', - }) - return workspaceIdResp - }), - vscode.workspace.onDidCreateFiles((e) => { - client.sendNotification('workspace/didCreateFiles', { - files: e.files.map((it) => { - return { uri: it.fsPath } - }), - } as CreateFilesParams) - }), - vscode.workspace.onDidDeleteFiles((e) => { - client.sendNotification('workspace/didDeleteFiles', { - files: e.files.map((it) => { - return { uri: it.fsPath } - }), - } as DeleteFilesParams) - }), - vscode.workspace.onDidRenameFiles((e) => { - client.sendNotification('workspace/didRenameFiles', { - files: e.files.map((it) => { - return { oldUri: it.oldUri.fsPath, newUri: it.newUri.fsPath } - }), - } as RenameFilesParams) - }), - vscode.workspace.onDidSaveTextDocument((e) => { - client.sendNotification('workspace/didSaveTextDocument', { - textDocument: { - uri: e.uri.fsPath, - }, - } as DidSaveTextDocumentParams) - }), - vscode.workspace.onDidChangeWorkspaceFolders((e) => { - client.sendNotification('workspace/didChangeWorkspaceFolder', { - event: { - added: e.added.map((it) => { - return { - name: it.name, - uri: it.uri.fsPath, - } as WorkspaceFolder - }), - removed: e.removed.map((it) => { - return { - name: it.name, - uri: it.uri.fsPath, - } as WorkspaceFolder - }), - }, - } as DidChangeWorkspaceFoldersParams) - }), - { dispose: () => clearInterval(refreshInterval) } - vscode.workspace.onDidCloseTextDocument(async () => { await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion') }) - ) } - - if (Experiments.instance.get('amazonqChatLSP', false)) { - activate(client, encryptionKey, resourcePaths.ui) + if (Experiments.instance.get('amazonqChatLSP', true)) { + await activateChat(client, encryptionKey, resourcePaths.ui) } + toDispose.push( + AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(sendProfileToLsp), + vscode.commands.registerCommand('aws.amazonq.getWorkspaceId', async () => { + const requestType = new RequestType( + 'aws/getConfigurationFromServer' + ) + const workspaceIdResp = await client.sendRequest(requestType.method, { + section: 'aws.q.workspaceContext', + }) + return workspaceIdResp + }), + vscode.workspace.onDidCreateFiles((e) => { + client.sendNotification('workspace/didCreateFiles', { + files: e.files.map((it) => { + return { uri: it.fsPath } + }), + } as CreateFilesParams) + }), + vscode.workspace.onDidDeleteFiles((e) => { + client.sendNotification('workspace/didDeleteFiles', { + files: e.files.map((it) => { + return { uri: it.fsPath } + }), + } as DeleteFilesParams) + }), + vscode.workspace.onDidRenameFiles((e) => { + client.sendNotification('workspace/didRenameFiles', { + files: e.files.map((it) => { + return { oldUri: it.oldUri.fsPath, newUri: it.newUri.fsPath } + }), + } as RenameFilesParams) + }), + vscode.workspace.onDidSaveTextDocument((e) => { + client.sendNotification('workspace/didSaveTextDocument', { + textDocument: { + uri: e.uri.fsPath, + }, + } as DidSaveTextDocumentParams) + }), + vscode.workspace.onDidChangeWorkspaceFolders((e) => { + client.sendNotification('workspace/didChangeWorkspaceFolder', { + event: { + added: e.added.map((it) => { + return { + name: it.name, + uri: it.uri.fsPath, + } as WorkspaceFolder + }), + removed: e.removed.map((it) => { + return { + name: it.name, + uri: it.uri.fsPath, + } as WorkspaceFolder + }), + }, + } as DidChangeWorkspaceFoldersParams) + }), + // Set this inside onReady so that it only triggers on subsequent language server starts (not the first) + onServerRestartHandler(client) + ) + return client } + +/** + * When the server restarts (likely due to a crash, then the LanguageClient automatically starts it again) + * we need to run some server intialization again. + */ +function onServerRestartHandler(client: LanguageClient) { + return client.onDidChangeState(async (e) => { + // Ensure we are in a "restart" state + if (!(e.oldState === State.Starting && e.newState === State.Running)) { + return + } + + // Emit telemetry that a crash was detected. + // It is not guaranteed to 100% be a crash since somehow the server may have been intentionally restarted, + // but most of the time it probably will have been due to a crash. + // TODO: Port this metric override to common definitions + telemetry.languageServer_crash.emit({ id: 'AmazonQ' }) + + // Need to set the auth token in the again + await AuthUtil.instance.restore() + }) +} + +function getConfigSection(section: ConfigSection) { + getLogger('amazonqLsp').debug('Fetching config section %s for language server', section) + switch (section) { + case 'aws.q': + /** + * IMPORTANT: This object is parsed by the following code in the language server, **so + * it must match that expected shape**. + * https://github.com/aws/language-servers/blob/1d2ca018f2248106690438b860d40a7ee67ac728/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/configurationUtils.ts#L114 + */ + return [ + { + customization: undefinedIfEmpty(getSelectedCustomization().arn), + optOutTelemetry: getOptOutPreference() === 'OPTOUT', + projectContext: { + enableLocalIndexing: CodeWhispererSettings.instance.isLocalIndexEnabled(), + enableGpuAcceleration: CodeWhispererSettings.instance.isLocalIndexGPUEnabled(), + indexWorkerThreads: CodeWhispererSettings.instance.getIndexWorkerThreads(), + localIndexing: { + ignoreFilePatterns: CodeWhispererSettings.instance.getIndexIgnoreFilePatterns(), + maxFileSizeMB: CodeWhispererSettings.instance.getMaxIndexFileSize(), + maxIndexSizeMB: CodeWhispererSettings.instance.getMaxIndexSize(), + indexCacheDirPath: CodeWhispererSettings.instance.getIndexCacheDirPath(), + }, + }, + }, + ] + case 'aws.codeWhisperer': + return [ + { + includeSuggestionsWithCodeReferences: + CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled(), + shareCodeWhispererContentWithAWS: !CodeWhispererSettings.instance.isOptoutEnabled(), + includeImportsWithSuggestions: CodeWhispererSettings.instance.isImportRecommendationEnabled(), + sendUserWrittenCodeMetrics: true, + }, + ] + case 'aws.logLevel': + return [toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel)] + } +} diff --git a/packages/amazonq/src/lsp/config.ts b/packages/amazonq/src/lsp/config.ts index a98afa44008..bb6870cb561 100644 --- a/packages/amazonq/src/lsp/config.ts +++ b/packages/amazonq/src/lsp/config.ts @@ -2,7 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - +import * as vscode from 'vscode' import { DevSettings, getServiceEnvVarConfig } from 'aws-core-vscode/shared' import { LspConfig } from 'aws-core-vscode/amazonq' @@ -10,9 +10,29 @@ export interface ExtendedAmazonQLSPConfig extends LspConfig { ui?: string } +// Taken from language server runtimes since they are not exported: +// https://github.com/aws/language-server-runtimes/blob/eae85672c345d8adaf4c8cbd741260b8a59750c4/runtimes/runtimes/util/loggingUtil.ts#L4-L10 +const validLspLogLevels = ['error', 'warn', 'info', 'log', 'debug'] as const +export type LspLogLevel = (typeof validLspLogLevels)[number] +const lspLogLevelMapping: Map = new Map([ + [vscode.LogLevel.Error, 'error'], + [vscode.LogLevel.Warning, 'warn'], + [vscode.LogLevel.Info, 'info'], + [vscode.LogLevel.Debug, 'log'], + [vscode.LogLevel.Trace, 'debug'], + [vscode.LogLevel.Off, 'error'], // TODO: once the language server supports a no-log setting, we can map to that. +]) + +const configSections = ['aws.q', 'aws.codeWhisperer', 'aws.logLevel'] as const +export type ConfigSection = (typeof configSections)[number] + +export function isValidConfigSection(section: unknown): section is ConfigSection { + return typeof section === 'string' && configSections.includes(section as ConfigSection) +} + export const defaultAmazonQLspConfig: ExtendedAmazonQLSPConfig = { - manifestUrl: 'https://aws-toolkit-language-servers.amazonaws.com/remoteWorkspaceContext/0/manifest.json', - supportedVersions: '^1.0.0', + manifestUrl: 'https://aws-toolkit-language-servers.amazonaws.com/qAgenticChatServer/0/manifest.json', + supportedVersions: '1.*.*', id: 'AmazonQ', // used across IDEs for identifying global storage/local disk locations. Do not change. suppressPromptPrefix: 'amazonQ', path: undefined, @@ -26,3 +46,11 @@ export function getAmazonQLspConfig(): ExtendedAmazonQLSPConfig { ...getServiceEnvVarConfig('amazonqLsp', Object.keys(defaultAmazonQLspConfig)), } } +/** + * The language server logging levels do not directly match those used in VSC. Therefore, we must perform a mapping defined by {@link lspLogLevelMapping} + * @param logLevel vscode log level (0-5) + * @returns language server log level + */ +export function toAmazonQLSPLogLevel(logLevel: vscode.LogLevel): LspLogLevel { + return lspLogLevelMapping.get(logLevel) ?? 'info' +} diff --git a/packages/amazonq/src/lsp/lspInstaller.ts b/packages/amazonq/src/lsp/lspInstaller.ts index 6374e609ec0..72fa091f027 100644 --- a/packages/amazonq/src/lsp/lspInstaller.ts +++ b/packages/amazonq/src/lsp/lspInstaller.ts @@ -40,4 +40,6 @@ export class AmazonQLspInstaller extends BaseLspInstaller.BaseLspInstaller< ui: path.join(assetDirectory, 'clients/amazonq-ui.js'), } } + + protected override downloadMessageOverride: string | undefined = 'Updating Amazon Q plugin' } diff --git a/packages/amazonq/test/e2e/amazonq/featureDev.test.ts b/packages/amazonq/test/e2e/amazonq/featureDev.test.ts index 5965128a144..87099e2a2d0 100644 --- a/packages/amazonq/test/e2e/amazonq/featureDev.test.ts +++ b/packages/amazonq/test/e2e/amazonq/featureDev.test.ts @@ -166,6 +166,12 @@ describe('Amazon Q Feature Dev', function () { describe('/dev {msg} entry', async () => { beforeEach(async function () { + const isMultiIterationTestsEnabled = process.env['AMAZONQ_FEATUREDEV_ITERATION_TEST'] // Controls whether to enable multiple iteration testing for Amazon Q feature development + if (!isMultiIterationTestsEnabled) { + this.skip() + } else { + this.timeout(900000) // Code Gen with multi-iterations requires longer than default timeout(5 mins). + } tab = framework.createTab() tab.addChatMessage({ command: '/dev', prompt }) tab = framework.getSelectedTab() diff --git a/packages/amazonq/test/e2e/amazonq/review.test.ts b/packages/amazonq/test/e2e/amazonq/review.test.ts index 5828d58e8d2..5914e3eaa29 100644 --- a/packages/amazonq/test/e2e/amazonq/review.test.ts +++ b/packages/amazonq/test/e2e/amazonq/review.test.ts @@ -5,7 +5,7 @@ */ import assert from 'assert' -import vscode from 'vscode' +import * as vscode from 'vscode' import { qTestingFramework } from './framework/framework' import sinon from 'sinon' import { Messenger } from './framework/messenger' @@ -16,9 +16,14 @@ import { invalidFileTypeChatMessage, CodeAnalysisScope, SecurityScanStep, + amazonqCodeIssueDetailsTabTitle, + CodeWhispererConstants, + CodeScanIssue, } from 'aws-core-vscode/codewhisperer' import path from 'path' import { ScanAction, scanProgressMessage } from '../../../src/app/amazonqScan/models/constants' +import { SecurityIssueProvider } from 'aws-core-vscode/codewhisperer' +import { fs, waitUntil, processUtils } from 'aws-core-vscode/shared' function getWorkspaceFolder(): string { return vscode.workspace.workspaceFolders![0].uri.fsPath @@ -59,23 +64,20 @@ describe('Amazon Q Code Review', function () { return issues } - function hasExactlyMatchingSecurityDiagnostic( - diagnostics: vscode.Diagnostic[], - code: string, - message: string, - startLine: number, - endLine: number, - count: number = 1 - ) { - const matchingDiagnostics = diagnostics.filter( - (diagnostic) => - diagnostic.code === code && - diagnostic.message === message && - diagnostic.range.start.line === startLine && - diagnostic.range.end.line === endLine - ) + function matchingSecurityDiagnosticCount(diagnostics: vscode.Diagnostic[], message: string, lineNumber?: number) { + const matchingDiagnostics = diagnostics.filter((diagnostic) => { + let matches = diagnostic.message === message + + // Only filter by startLine if it's provided + if (lineNumber !== undefined) { + matches = + matches && diagnostic.range.start.line <= lineNumber && diagnostic.range.end.line >= lineNumber + } - assert.deepEqual(matchingDiagnostics.length, count) + return matches + }) + + return matchingDiagnostics.length } async function waitForChatItems(index: number, waitTimeoutInMs: number = 5000, waitIntervalInMs: number = 1000) { @@ -172,17 +174,15 @@ describe('Amazon Q Code Review', function () { }) }) - describe('review insecure file or project', async () => { - const testFolder = path.join(getWorkspaceFolder(), 'QCAFolder') - const fileName = 'ProblematicCode.java' - const filePath = path.join(testFolder, fileName) + describe('review insecure file and then fix file', async () => { + it('/review file gives correct critical and high security issues, clicks on view details, generate fix, verify diff, apply fix', async () => { + const testFolder = path.join(getWorkspaceFolder(), 'QCAFolder') + const fileName = 'ProblematicCode.java' + const filePath = path.join(testFolder, fileName) - beforeEach(async () => { await validateInitialChatMessage() - }) - - it.skip('/review file gives correct critical and high security issues', async () => { const document = await vscode.workspace.openTextDocument(filePath) + const originalContent = document.getText() await vscode.window.showTextDocument(document) tab.clickButton(ScanAction.RUN_FILE_SCAN) @@ -204,25 +204,203 @@ describe('Amazon Q Code Review', function () { ) const uri = vscode.Uri.file(filePath) - const securityDiagnostics: vscode.Diagnostic[] = vscode.languages + const securityDiagnostics = vscode.languages .getDiagnostics(uri) .filter((diagnostic) => diagnostic.source === codewhispererDiagnosticSourceLabel) - // 1 exact critical issue matches - hasExactlyMatchingSecurityDiagnostic( - securityDiagnostics, - 'java-do-not-hardcode-database-password', - 'CWE-798 - Hardcoded credentials', - 20, - 21 + assert.ok(securityDiagnostics.length > 0, 'No security diagnostics found') + + // at least 1 exact critical issue matches + assert.ok( + matchingSecurityDiagnosticCount(securityDiagnostics, 'CWE-798 - Hardcoded credentials', 21) >= 1 ) - }) - it('/review project gives findings', async () => { - tab.clickButton(ScanAction.RUN_PROJECT_SCAN) + // Find one diagnostic + const sampleDiagnostic = securityDiagnostics[0] + assert.ok(sampleDiagnostic, 'Could not find critical issue diagnostic') - const scanResultBody = await waitForReviewResults(tab) - extractAndValidateIssues(scanResultBody) + const range = new vscode.Range(sampleDiagnostic.range.start, sampleDiagnostic.range.end) + + const codeActions = await vscode.commands.executeCommand( + 'vscode.executeCodeActionProvider', + uri, + range + ) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // Find the "View details" code action + const viewDetailsAction = codeActions?.find((action) => action.title.includes('View details')) + assert.ok(viewDetailsAction, 'Could not find View details code action') + + // Execute the view details command + if (viewDetailsAction?.command) { + await vscode.commands.executeCommand( + viewDetailsAction.command.command, + ...viewDetailsAction.command.arguments! + ) + } + + // Wait for the webview panel to open with polling + const webviewPanel = await waitUntil( + async () => { + const panels = vscode.window.tabGroups.all + .flatMap((group) => group.tabs) + .filter((tab) => tab.label === amazonqCodeIssueDetailsTabTitle) + .map((tab) => tab.input) + .filter((input): input is vscode.WebviewPanel => input !== undefined) + + return panels.length > 0 ? panels[0] : undefined + }, + { + timeout: 20_000, + interval: 1000, + truthy: false, + } + ) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + assert.ok(webviewPanel, 'Security issue webview panel did not open after waiting') + + // Wait until viewDetailsAction.command is defined + const viewDetailsActionDefined = await waitUntil( + async () => { + return viewDetailsAction.command?.arguments !== undefined + ? viewDetailsAction.command + : undefined + }, + { + timeout: 10_000, + interval: 500, + truthy: true, + } + ) + await new Promise((resolve) => setTimeout(resolve, 1000)) + + assert.ok(viewDetailsActionDefined, 'viewDetailsAction.command was not defined after waiting') + + const issue = viewDetailsActionDefined.arguments?.[0] as CodeScanIssue + console.log('issue', issue) + + // Wait for the fix to be generated with polling + const updatedIssue = await waitUntil( + async () => { + const foundIssue = SecurityIssueProvider.instance.issues + .flatMap(({ issues }) => issues) + .find((i) => i.findingId === issue.findingId) + + return foundIssue?.suggestedFixes?.length !== undefined && + foundIssue?.suggestedFixes?.length > 0 + ? foundIssue + : undefined + }, + { + timeout: CodeWhispererConstants.codeFixJobTimeoutMs + 20_000, + interval: CodeWhispererConstants.codeFixJobPollingIntervalMs, + truthy: true, + } + ) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + console.log('updated issue', updatedIssue) + console.log('original issue', issue) + + // Verify the fix was generated by checking if the issue has suggestedFixes + assert.ok(updatedIssue, 'Could not find updated issue') + assert.ok(updatedIssue.suggestedFixes.length > 0, 'No suggested fixes were generated') + + // Get the suggested fix and verify it contains diff markers + const suggestedFix = updatedIssue.suggestedFixes[0] + const suggestedFixDiff = suggestedFix.code + assert.ok(suggestedFixDiff, 'No suggested fix code was found') + assert.ok( + suggestedFixDiff.includes('-') && suggestedFixDiff.includes('+'), + 'Suggested fix does not contain diff markers' + ) + + // Parse the diff to extract removed and added lines + const diffLines = suggestedFixDiff.split('\n') + const removedLines = diffLines + .filter((line) => line.startsWith('-') && !line.startsWith('---')) + .map((line) => line.substring(1).trim()) + const addedLines = diffLines + .filter((line) => line.startsWith('+') && !line.startsWith('+++')) + .map((line) => line.substring(1).trim()) + + // Make sure we found some changes in the diff + assert.ok(addedLines.length + removedLines.length > 0, 'No added or deleted lines found in the diff') + + // Apply the fix + await vscode.commands.executeCommand('aws.amazonq.applySecurityFix', updatedIssue, filePath, 'webview') + + // Wait for the fix to be applied + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // Verify the fix was applied to the file + const updatedDocument = await vscode.workspace.openTextDocument(filePath) + const updatedContent = updatedDocument.getText() + + // Check that the content has changed + assert.notStrictEqual( + updatedContent, + originalContent, + 'File content did not change after applying the fix' + ) + + // Count occurrences of each line in original and updated content + const countOccurrences = (text: string, line: string): number => { + const regex = new RegExp(`^\\s*${line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'gm') + const matches = text.match(regex) + return matches ? matches.length : 0 + } + + // Create a dictionary to track expected line count changes + const lineCountChanges: Record = {} + + // Process removed lines (decrement count) + for (const removedLine of removedLines) { + if (removedLine.trim()) { + // Skip empty lines + const trimmedLine = removedLine.trim() + lineCountChanges[trimmedLine] = (lineCountChanges[trimmedLine] || 0) - 1 + } + } + + // Process added lines (increment count) + for (const addedLine of addedLines) { + if (addedLine.trim()) { + // Skip empty lines + const trimmedLine = addedLine.trim() + lineCountChanges[trimmedLine] = (lineCountChanges[trimmedLine] || 0) + 1 + } + } + + // Verify all line count changes match expectations + for (const [line, expectedChange] of Object.entries(lineCountChanges)) { + const originalCount = countOccurrences(originalContent, line) + const updatedCount = countOccurrences(updatedContent, line) + const actualChange = updatedCount - originalCount + + assert.strictEqual( + actualChange, + expectedChange, + `Line "${line}" count change mismatch: expected ${expectedChange}, got ${actualChange} (original: ${originalCount}, updated: ${updatedCount})` + ) + } + + // Revert the changes + await fs.writeFile(uri, originalContent) + + // Wait a moment for the file system to update + await new Promise((resolve) => setTimeout(resolve, 1000)) + + // Verify the file was reverted + const revertedDocument = await vscode.workspace.openTextDocument(filePath) + const revertedContent = revertedDocument.getText() + + assert.deepStrictEqual(revertedContent, originalContent, 'File content was not properly reverted') }) }) @@ -230,11 +408,12 @@ describe('Amazon Q Code Review', function () { const testFolder = path.join(getWorkspaceFolder(), 'QCAFolder') const fileName = 'ProblematicCode.java' const filePath = path.join(testFolder, fileName) + let document: vscode.TextDocument beforeEach(async () => { await validateInitialChatMessage() - const document = await vscode.workspace.openTextDocument(filePath) + document = await vscode.workspace.openTextDocument(filePath) await vscode.window.showTextDocument(document) const editor = vscode.window.activeTextEditor @@ -244,6 +423,7 @@ describe('Amazon Q Code Review', function () { await editor.edit((editBuilder) => { editBuilder.insert(position, '// amazonq-ignore-next-line\n') }) + await document.save() } }) @@ -264,12 +444,8 @@ describe('Amazon Q Code Review', function () { .filter((diagnostic) => diagnostic.source === codewhispererDiagnosticSourceLabel) // cannot find this ignored issue - hasExactlyMatchingSecurityDiagnostic( - securityDiagnostics, - 'java-do-not-hardcode-database-password', - 'CWE-798 - Hardcoded credentials', - 21, - 22, + assert.equal( + matchingSecurityDiagnosticCount(securityDiagnostics, 'CWE-798 - Hardcoded credentials', 22), 0 ) @@ -279,6 +455,155 @@ describe('Amazon Q Code Review', function () { const lineRange = editor.document.lineAt(20).rangeIncludingLineBreak editBuilder.delete(lineRange) }) + await document.save() + } + }) + }) + + describe('Project and file scans should return at least 1 LLM findings', async () => { + const testFolder = path.join(getWorkspaceFolder(), 'QCAFolder') + const fileName = 'RLinker.java' + const filePath = path.join(testFolder, fileName) + let document: vscode.TextDocument + + function assertAtLeastOneLLMFindings(securityDiagnostics: vscode.Diagnostic[]) { + const readabilityIssuesCount = matchingSecurityDiagnosticCount( + securityDiagnostics, + 'Readability and maintainability issues detected.' + ) + const performanceIssuesCount = matchingSecurityDiagnosticCount( + securityDiagnostics, + 'Performance inefficiencies detected in code.' + ) + const errorHandlingIssuesCount = matchingSecurityDiagnosticCount( + securityDiagnostics, + 'Inadequate error handling detected.' + ) + const namingIssuesCount = matchingSecurityDiagnosticCount( + securityDiagnostics, + 'Inconsistent or unclear naming detected.' + ) + const loggingIssuesCount = matchingSecurityDiagnosticCount( + securityDiagnostics, + 'Insufficient or improper logging found.' + ) + assert.ok( + readabilityIssuesCount + + performanceIssuesCount + + errorHandlingIssuesCount + + namingIssuesCount + + loggingIssuesCount > + 0, + 'No LLM findings were found' + ) + } + + beforeEach(async () => { + await validateInitialChatMessage() + }) + + it('file scan returns at least 1 LLM findings', async () => { + document = await vscode.workspace.openTextDocument(filePath) + await vscode.window.showTextDocument(document) + + tab.clickButton(ScanAction.RUN_FILE_SCAN) + + await waitForChatItems(6) + const scanningInProgressMessage = tab.getChatItems()[6] + assert.deepStrictEqual( + scanningInProgressMessage.body, + scanProgressMessage(SecurityScanStep.CREATE_SCAN_JOB, CodeAnalysisScope.FILE_ON_DEMAND, fileName) + ) + + const scanResultBody = await waitForReviewResults(tab) + + const issues = extractAndValidateIssues(scanResultBody) + + assert.deepStrictEqual( + issues.Critical + issues.High + issues.Medium + issues.Low + issues.Info >= 1, + true, + `There are no issues detected when there should be at least 1` + ) + + const uri = vscode.Uri.file(filePath) + const securityDiagnostics: vscode.Diagnostic[] = vscode.languages + .getDiagnostics(uri) + .filter((diagnostic) => diagnostic.source === codewhispererDiagnosticSourceLabel) + + assertAtLeastOneLLMFindings(securityDiagnostics) + }) + + it('project scan returns at least 1 LLM findings', async () => { + const fileDir = path.join(getWorkspaceFolder()) + + try { + // Initialize git repository to make RLinker.java appear in git diff + await processUtils.ChildProcess.run('git', ['init'], { spawnOptions: { cwd: fileDir } }) + await processUtils.ChildProcess.run('git', ['add', 'QCAFolder/RLinker.java'], { + spawnOptions: { cwd: fileDir }, + }) + await processUtils.ChildProcess.run('git', ['config', 'user.name', 'Test'], { + spawnOptions: { cwd: fileDir }, + }) + await processUtils.ChildProcess.run('git', ['config', 'user.email', 'test@example.com'], { + spawnOptions: { cwd: fileDir }, + }) + await processUtils.ChildProcess.run('git', ['commit', '-m', 'Initial commit'], { + spawnOptions: { cwd: fileDir }, + }) + + document = await vscode.workspace.openTextDocument(filePath) + await vscode.window.showTextDocument(document) + + const editor = vscode.window.activeTextEditor + + if (editor) { + const position = new vscode.Position(20, 0) + await editor.edit((editBuilder) => { + editBuilder.insert(position, '\n') + }) + // save file + await document.save() + } + + // Run the project scan + tab.clickButton(ScanAction.RUN_PROJECT_SCAN) + + await waitForChatItems(6) + const scanningInProgressMessage = tab.getChatItems()[6] + assert.deepStrictEqual( + scanningInProgressMessage.body, + scanProgressMessage(SecurityScanStep.CREATE_SCAN_JOB, CodeAnalysisScope.PROJECT) + ) + + const scanResultBody = await waitForReviewResults(tab) + + const issues = extractAndValidateIssues(scanResultBody) + + assert.deepStrictEqual( + issues.Critical + issues.High + issues.Medium + issues.Low + issues.Info >= 1, + true, + `There are no issues detected when there should be at least 1` + ) + + const uri = vscode.Uri.file(filePath) + const securityDiagnostics: vscode.Diagnostic[] = vscode.languages + .getDiagnostics(uri) + .filter((diagnostic) => diagnostic.source === codewhispererDiagnosticSourceLabel) + + assertAtLeastOneLLMFindings(securityDiagnostics) + + const editor2 = vscode.window.activeTextEditor + if (editor2) { + await editor2.edit((editBuilder) => { + const lineRange = editor2.document.lineAt(20).rangeIncludingLineBreak + editBuilder.delete(lineRange) + }) + await document.save() + } + } finally { + // Clean up git repository + await fs.delete(path.join(fileDir, '.git'), { recursive: true }) } }) }) diff --git a/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts b/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts index 784aed4884c..74f788732dd 100644 --- a/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts +++ b/packages/amazonq/test/e2e/amazonq/transformByQ.test.ts @@ -75,6 +75,9 @@ describe('Amazon Q Code Transformation', function () { }, ]) + transformByQState.setSourceJDKVersion(JDKVersion.JDK8) + transformByQState.setTargetJDKVersion(JDKVersion.JDK17) + tab.addChatMessage({ command: '/transform' }) // wait for /transform to respond with some intro messages and the first user input form @@ -141,16 +144,34 @@ describe('Amazon Q Code Transformation', function () { formItemValues: oneOrMultipleDiffsFormValues, }) - // 2 additional chat messages (including message with 4th form) get sent after 3rd form submitted; wait for both of them + // 2 additional chat messages get sent after 3rd form submitted; wait for both of them await tab.waitForEvent(() => tab.getChatItems().length > 11, { waitTimeoutInMs: 5000, waitIntervalInMs: 1000, }) - const jdkPathPrompt = tab.getChatItems().pop() - assert.strictEqual(jdkPathPrompt?.body?.includes('Enter the path to JDK'), true) - // 2 additional chat messages get sent after 4th form submitted; wait for both of them + // TO-DO: add this back when releasing CSB + /* + const customDependencyVersionPrompt = tab.getChatItems().pop() + assert.strictEqual( + customDependencyVersionPrompt?.body?.includes('You can optionally upload a YAML file'), + true + ) + tab.clickCustomFormButton({ id: 'gumbyTransformFormContinue' }) + + // 2 additional chat messages get sent after Continue button clicked; wait for both of them + await tab.waitForEvent(() => tab.getChatItems().length > 13, { + waitTimeoutInMs: 5000, + waitIntervalInMs: 1000, + }) + */ + + const sourceJdkPathPrompt = tab.getChatItems().pop() + assert.strictEqual(sourceJdkPathPrompt?.body?.includes('Enter the path to JDK 8'), true) + tab.addChatMessage({ prompt: '/dummy/path/to/jdk8' }) + + // 2 additional chat messages get sent after JDK path submitted; wait for both of them await tab.waitForEvent(() => tab.getChatItems().length > 13, { waitTimeoutInMs: 5000, waitIntervalInMs: 1000, @@ -401,7 +422,7 @@ describe('Amazon Q Code Transformation', function () { it('WHEN transforming a Java 8 project E2E THEN job is successful', async function () { transformByQState.setTransformationType(TransformationType.LANGUAGE_UPGRADE) - await setMaven() + setMaven() await startTransformByQ.processLanguageUpgradeTransformFormInput(tempDir, JDKVersion.JDK8, JDKVersion.JDK17) await startTransformByQ.startTransformByQ() assert.strictEqual(transformByQState.getPolledJobStatus(), 'COMPLETED') diff --git a/packages/amazonq/test/e2e/inline/inline.test.ts b/packages/amazonq/test/e2e/inline/inline.test.ts index 57c6e1c4996..43a9f67ab73 100644 --- a/packages/amazonq/test/e2e/inline/inline.test.ts +++ b/packages/amazonq/test/e2e/inline/inline.test.ts @@ -122,7 +122,7 @@ describe('Amazon Q Inline', async function () { .query({ metricName: 'codewhisperer_userTriggerDecision', }) - .map((e) => collectionUtil.partialClone(e, 3, ['credentialStartUrl'], '[omitted]')) + .map((e) => collectionUtil.partialClone(e, 3, ['credentialStartUrl'], { replacement: '[omitted]' })) } for (const [name, invokeCompletion] of [ diff --git a/packages/amazonq/test/e2e/lsp/lspInstallerUtil.ts b/packages/amazonq/test/e2e/lsp/lspInstallerUtil.ts index 4c88cba491b..c7ca7a4ff9b 100644 --- a/packages/amazonq/test/e2e/lsp/lspInstallerUtil.ts +++ b/packages/amazonq/test/e2e/lsp/lspInstallerUtil.ts @@ -14,6 +14,7 @@ import { ManifestResolver, request, TargetContent, + ToolkitError, } from 'aws-core-vscode/shared' import * as semver from 'semver' import { assertTelemetry } from 'aws-core-vscode/test' @@ -201,12 +202,6 @@ export function createLspInstallerTests({ id: lspConfig.id, manifestLocation: 'remote', languageServerSetupStage: 'getManifest', - result: 'Failed', - }, - { - id: lspConfig.id, - manifestLocation: 'cache', - languageServerSetupStage: 'getManifest', result: 'Succeeded', }, { @@ -282,6 +277,34 @@ export function createLspInstallerTests({ const download = await createInstaller(lspConfig).resolve() assert.ok(download.assetDirectory.endsWith('-rc.0')) }) + + it('throws on firewall error', async () => { + // Stub the manifest resolver to return a valid manifest + sandbox.stub(ManifestResolver.prototype, 'resolve').resolves({ + manifestSchemaVersion: '0.0.0', + artifactId: 'foo', + artifactDescription: 'foo', + isManifestDeprecated: false, + versions: [createVersion('1.0.0', targetContents)], + }) + + // Fail all HTTP requests for the language server + sandbox.stub(request, 'fetch').returns({ + response: Promise.resolve({ + ok: false, + }), + } as any) + + // This should now throw a NetworkConnectivityError + await assert.rejects( + async () => await installer.resolve(), + (err: ToolkitError) => { + assert.strictEqual(err.code, 'NetworkConnectivityError') + assert.ok(err.message.includes('Unable to download dependencies')) + return true + } + ) + }) }) }) } diff --git a/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts b/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts new file mode 100644 index 00000000000..5d9972019f4 --- /dev/null +++ b/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts @@ -0,0 +1,120 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'assert' +import * as sinon from 'sinon' +import { assertTelemetry, createTestAuthUtil } from 'aws-core-vscode/test' +import { AuthUtil, awsIdSignIn, getStartUrl } from 'aws-core-vscode/codewhisperer' +import { backendAmazonQ } from 'aws-core-vscode/login' + +describe('Amazon Q Login', async function () { + const region = 'fakeRegion' + const startUrl = 'fakeUrl' + + let sandbox: sinon.SinonSandbox + let backend: backendAmazonQ.AmazonQLoginWebview + + beforeEach(async function () { + await createTestAuthUtil() + sandbox = sinon.createSandbox() + backend = new backendAmazonQ.AmazonQLoginWebview() + }) + + afterEach(function () { + sandbox.restore() + }) + + it('signs into builder ID and emits telemetry', async function () { + await backend.startBuilderIdSetup() + + assert.ok(AuthUtil.instance.isConnected()) + assert.ok(AuthUtil.instance.isBuilderIdConnection()) + + assertTelemetry('auth_addConnection', { + result: 'Succeeded', + credentialSourceId: 'awsId', + authEnabledFeatures: 'codewhisperer', + isReAuth: false, + ssoRegistrationExpiresAt: undefined, + ssoRegistrationClientId: undefined, + }) + }) + + it('signs into IdC and emits telemetry', async function () { + await backend.startEnterpriseSetup(startUrl, region) + + assert.ok(AuthUtil.instance.isConnected()) + assert.ok(AuthUtil.instance.isIdcConnection()) + assert.ok(AuthUtil.instance.isSsoSession()) + assert.deepStrictEqual(AuthUtil.instance.connection?.startUrl, startUrl) + assert.deepStrictEqual(AuthUtil.instance.connection?.region, region) + + assertTelemetry('auth_addConnection', { + result: 'Succeeded', + credentialSourceId: 'iamIdentityCenter', + authEnabledFeatures: 'codewhisperer', + credentialStartUrl: startUrl, + awsRegion: region, + isReAuth: false, + ssoRegistrationExpiresAt: undefined, + ssoRegistrationClientId: undefined, + }) + }) + + it('reauths builder ID and emits telemetry', async function () { + await awsIdSignIn() + + await backend.reauthenticateConnection() + + assert.ok(AuthUtil.instance.isConnected()) + + assertTelemetry('auth_addConnection', { + result: 'Succeeded', + credentialSourceId: 'awsId', + authEnabledFeatures: 'codewhisperer', + isReAuth: true, + ssoRegistrationExpiresAt: undefined, + ssoRegistrationClientId: undefined, + }) + }) + + it('reauths IdC and emits telemetry', async function () { + await getStartUrl.connectToEnterpriseSso(startUrl, region) + + await backend.reauthenticateConnection() + + assert.ok(AuthUtil.instance.isConnected()) + + assertTelemetry('auth_addConnection', { + result: 'Succeeded', + credentialSourceId: 'iamIdentityCenter', + authEnabledFeatures: 'codewhisperer', + credentialStartUrl: startUrl, + awsRegion: region, + isReAuth: true, + ssoRegistrationExpiresAt: undefined, + ssoRegistrationClientId: undefined, + }) + }) + + it('signs out of reauth and emits telemetry', async function () { + await getStartUrl.connectToEnterpriseSso(startUrl, region) + + await backend.signout() + + assert.ok(!AuthUtil.instance.isConnected()) + + assertTelemetry('auth_addConnection', { + result: 'Cancelled', + credentialSourceId: 'iamIdentityCenter', + authEnabledFeatures: 'codewhisperer', + credentialStartUrl: startUrl, + awsRegion: region, + isReAuth: true, + ssoRegistrationExpiresAt: undefined, + ssoRegistrationClientId: undefined, + }) + }) +}) diff --git a/packages/amazonq/test/unit/amazonq/lsp/chat/error.test.ts b/packages/amazonq/test/unit/amazonq/lsp/chat/error.test.ts new file mode 100644 index 00000000000..80bfe657cc1 --- /dev/null +++ b/packages/amazonq/test/unit/amazonq/lsp/chat/error.test.ts @@ -0,0 +1,15 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { isValidResponseError } from '../../../../../src/lsp/chat/error' +import { ResponseError } from '@aws/language-server-runtimes/protocol' +import * as assert from 'assert' + +describe('isValidResponseError', async function () { + it('requires the data field', function () { + assert.ok(isValidResponseError(new ResponseError(0, 'this one has data', {}))) + assert.ok(!isValidResponseError(new ResponseError(0, 'this one does not have data'))) + }) +}) diff --git a/packages/amazonq/test/unit/amazonqGumby/transformApiHandler.test.ts b/packages/amazonq/test/unit/amazonqGumby/transformApiHandler.test.ts index 6ee0f05391d..1441171bafc 100644 --- a/packages/amazonq/test/unit/amazonqGumby/transformApiHandler.test.ts +++ b/packages/amazonq/test/unit/amazonqGumby/transformApiHandler.test.ts @@ -6,6 +6,7 @@ import assert from 'assert' import { TransformationProgressUpdate, TransformationStep, + findDownloadArtifactProgressUpdate, findDownloadArtifactStep, getArtifactsFromProgressUpdate, } from 'aws-core-vscode/codewhisperer/node' @@ -95,4 +96,55 @@ describe('Amazon Q Transform - transformApiHandler tests', function () { assert.strictEqual(progressUpdate, undefined) }) }) + + describe('findDownloadArtifactProgressUpdate', function () { + it('will return correct progress update from transformationStep', function () { + const transformationStepsFixture: TransformationStep[] = [ + { + id: 'dummy-id', + name: 'Step name', + description: 'Step description', + status: 'TRANSFORMING', + progressUpdates: [ + { + name: 'Progress update name', + status: 'AWAITING_CLIENT_ACTION', + description: 'Client-side build happening now', + startTime: new Date(), + endTime: new Date(), + downloadArtifacts: [ + { + downloadArtifactId: 'some-download-artifact-id', + downloadArtifactType: 'some-download-artifact-type', + }, + ], + }, + ], + startTime: new Date(), + endTime: new Date(), + }, + ] + const progressUpdate = findDownloadArtifactProgressUpdate(transformationStepsFixture) + assert.strictEqual(progressUpdate, transformationStepsFixture[0].progressUpdates?.[0]) + }) + + it('will return undefined if step status is NOT AWAITING_CLIENT_ACTION', function () { + const transformationStepsFixture: TransformationStep[] = [ + { + id: 'random-id', + name: 'not-awaiting-client-action step name', + description: 'not-awaiting-client-action step description', + status: 'TRANSFORMING', + progressUpdates: [ + { + name: 'some progress update name', + status: 'SOMETHING-BESIDES-AWAITING_CLIENT_ACTION', + }, + ], + }, + ] + const progressUpdate = findDownloadArtifactProgressUpdate(transformationStepsFixture) + assert.strictEqual(progressUpdate, undefined) + }) + }) }) diff --git a/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts b/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts index 68cebe37bb1..56f72edfd3f 100644 --- a/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/commands/invokeRecommendation.test.ts @@ -5,7 +5,7 @@ import assert from 'assert' import * as sinon from 'sinon' -import { resetCodeWhispererGlobalVariables, createMockTextEditor } from 'aws-core-vscode/test' +import { resetCodeWhispererGlobalVariables, createMockTextEditor, createTestAuthUtil } from 'aws-core-vscode/test' import { ConfigurationEntry, invokeRecommendation, @@ -20,6 +20,7 @@ describe('invokeRecommendation', function () { let mockClient: DefaultCodeWhispererClient beforeEach(async function () { + await createTestAuthUtil() await resetCodeWhispererGlobalVariables() getRecommendationStub = sinon.stub(InlineCompletionService.instance, 'getPaginatedRecommendation') }) diff --git a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index 1429618b6e4..aa79e9052bd 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -8,11 +8,12 @@ import assert, { fail } from 'assert' import { AuthUtil, RegionProfile, RegionProfileManager, defaultServiceConfig } from 'aws-core-vscode/codewhisperer' import { globals } from 'aws-core-vscode/shared' import { constants } from 'aws-core-vscode/auth' +import { createTestAuthUtil } from 'aws-core-vscode/test' const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' const region = 'us-east-1' -describe('RegionProfileManager', function () { +describe('RegionProfileManager', async function () { let regionProfileManager: RegionProfileManager const profileFoo: RegionProfile = { @@ -34,7 +35,8 @@ describe('RegionProfileManager', function () { } } - beforeEach(function () { + beforeEach(async function () { + await createTestAuthUtil() regionProfileManager = new RegionProfileManager(AuthUtil.instance) }) @@ -59,12 +61,12 @@ describe('RegionProfileManager', function () { const mockClient = { listAvailableProfiles: listProfilesStub, } - const createClientStub = sinon.stub(regionProfileManager, 'createQClient').resolves(mockClient) + const createClientStub = sinon.stub(regionProfileManager, '_createQClient').resolves(mockClient) - const r = await regionProfileManager.listRegionProfiles() + const profileList = await regionProfileManager.listRegionProfile() - assert.strictEqual(r.length, 2) - assert.deepStrictEqual(r, [ + assert.strictEqual(profileList.length, 2) + assert.deepStrictEqual(profileList, [ { name: 'foo', arn: 'arn', @@ -177,7 +179,7 @@ describe('RegionProfileManager', function () { }) it(`restoreRegionProfile`, async function () { - sinon.stub(regionProfileManager, 'listRegionProfiles').resolves([profileFoo]) + sinon.stub(regionProfileManager, 'listRegionProfile').resolves([profileFoo]) await setupConnection('idc') if (!AuthUtil.instance.isConnected()) { fail('connection should not be undefined') @@ -226,12 +228,64 @@ describe('RegionProfileManager', function () { }) describe('createQClient', function () { + it(`should configure the endpoint and region from a profile`, async function () { + await setupConnection('idc') + + const iadClient = await regionProfileManager.createQClient({ + name: 'foo', + region: 'us-east-1', + arn: 'arn', + description: 'description', + }) + + assert.deepStrictEqual(iadClient.config.region, 'us-east-1') + assert.deepStrictEqual(iadClient.endpoint.href, 'https://q.us-east-1.amazonaws.com/') + + const fraClient = await regionProfileManager.createQClient({ + name: 'bar', + region: 'eu-central-1', + arn: 'arn', + description: 'description', + }) + + assert.deepStrictEqual(fraClient.config.region, 'eu-central-1') + assert.deepStrictEqual(fraClient.endpoint.href, 'https://q.eu-central-1.amazonaws.com/') + }) + + it(`should throw if the region is not supported or recognizable by Q`, async function () { + await setupConnection('idc') + + await assert.rejects( + async () => { + await regionProfileManager.createQClient({ + name: 'foo', + region: 'ap-east-1', + arn: 'arn', + description: 'description', + }) + }, + { message: /trying to initiatize Q client with unrecognizable region/ } + ) + + await assert.rejects( + async () => { + await regionProfileManager.createQClient({ + name: 'foo', + region: 'unknown-somewhere', + arn: 'arn', + description: 'description', + }) + }, + { message: /trying to initiatize Q client with unrecognizable region/ } + ) + }) + it(`should configure the endpoint and region correspondingly`, async function () { await setupConnection('idc') await regionProfileManager.switchRegionProfile(profileFoo, 'user') assert.deepStrictEqual(regionProfileManager.activeRegionProfile, profileFoo) - const client = await regionProfileManager.createQClient('eu-central-1', 'https://amazon.com/') + const client = await regionProfileManager._createQClient('eu-central-1', 'https://amazon.com/') assert.deepStrictEqual(client.config.region, 'eu-central-1') assert.deepStrictEqual(client.endpoint.href, 'https://amazon.com/') diff --git a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts index f24ce9d3f89..dd0bd65505f 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/inlineCompletionService.test.ts @@ -19,7 +19,12 @@ import { listCodeWhispererCommandsId, DefaultCodeWhispererClient, } from 'aws-core-vscode/codewhisperer' -import { createMockTextEditor, resetCodeWhispererGlobalVariables, createMockDocument } from 'aws-core-vscode/test' +import { + createMockTextEditor, + resetCodeWhispererGlobalVariables, + createMockDocument, + createTestAuthUtil, +} from 'aws-core-vscode/test' describe('inlineCompletionService', function () { beforeEach(async function () { @@ -192,6 +197,7 @@ describe('codewhisperer status bar', function () { } beforeEach(async function () { + await createTestAuthUtil() await resetCodeWhispererGlobalVariables() sandbox = sinon.createSandbox() statusBar = new TestStatusBar() diff --git a/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts index 4b6a5291f22..f3fa7b399d1 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/keyStrokeHandler.test.ts @@ -9,6 +9,7 @@ import * as sinon from 'sinon' import * as codewhispererSdkClient from 'aws-core-vscode/codewhisperer' import { createMockTextEditor, + createTestAuthUtil, createTextDocumentChangeEvent, resetCodeWhispererGlobalVariables, } from 'aws-core-vscode/test' @@ -160,13 +161,16 @@ describe('keyStrokeHandler', function () { describe('invokeAutomatedTrigger', function () { let mockClient: codewhispererSdkClient.DefaultCodeWhispererClient + beforeEach(async function () { + await createTestAuthUtil() sinon.restore() mockClient = new codewhispererSdkClient.DefaultCodeWhispererClient() await resetCodeWhispererGlobalVariables() sinon.stub(mockClient, 'listRecommendations') sinon.stub(mockClient, 'generateRecommendations') }) + afterEach(function () { sinon.restore() }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts index 947eeefabd4..d8d04516e85 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/recommendationHandler.test.ts @@ -9,13 +9,13 @@ import * as sinon from 'sinon' import { ReferenceInlineProvider, session, - AuthUtil, DefaultCodeWhispererClient, RecommendationsList, ConfigurationEntry, RecommendationHandler, CodeWhispererCodeCoverageTracker, supplementalContextUtil, + AuthUtil, } from 'aws-core-vscode/codewhisperer' import { assertTelemetryCurried, @@ -39,7 +39,6 @@ describe('recommendationHandler', function () { describe('getRecommendations', async function () { const mockClient = stub(DefaultCodeWhispererClient) const mockEditor = createMockTextEditor() - const testStartUrl = 'testStartUrl' beforeEach(async function () { sinon.restore() @@ -47,7 +46,6 @@ describe('recommendationHandler', function () { mockClient.listRecommendations.resolves({}) mockClient.generateRecommendations.resolves({}) RecommendationHandler.instance.clearRecommendations() - sinon.stub(AuthUtil.instance.connection!, 'startUrl').value(testStartUrl) }) afterEach(function () { @@ -143,7 +141,7 @@ describe('recommendationHandler', function () { codewhispererLineNumber: 1, codewhispererCursorOffset: 38, codewhispererLanguage: 'python', - credentialStartUrl: testStartUrl, + credentialStartUrl: AuthUtil.instance.connection?.startUrl, codewhispererSupplementalContextIsUtg: false, codewhispererSupplementalContextTimeout: false, codewhispererSupplementalContextLatency: 0, diff --git a/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts b/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts index 974a78c1943..a43720c81be 100644 --- a/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/tracker/codewhispererTracker.test.ts @@ -82,8 +82,6 @@ describe('codewhispererTracker', function () { describe('emitTelemetryOnSuggestion', function () { it('Should call recordCodewhispererUserModification with suggestion event', async function () { - const testStartUrl = 'testStartUrl' - sinon.stub(AuthUtil.instance.connection!, 'startUrl').value(testStartUrl) const suggestion = createAcceptedSuggestionEntry() const assertTelemetry = assertTelemetryCurried('codewhisperer_userModification') await CodeWhispererTracker.getTracker().emitTelemetryOnSuggestion(suggestion) @@ -95,7 +93,7 @@ describe('codewhispererTracker', function () { codewhispererModificationPercentage: 1, codewhispererCompletionType: 'Line', codewhispererLanguage: 'java', - credentialStartUrl: testStartUrl, + credentialStartUrl: AuthUtil.instance.connection?.startUrl, codewhispererCharactersAccepted: suggestion.originalString.length, codewhispererCharactersModified: 0, }) diff --git a/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts index e4f73c4df05..fa2956f5cf7 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/authUtil.test.ts @@ -3,420 +3,194 @@ * SPDX-License-Identifier: Apache-2.0 */ -// import assert from 'assert' -// import { -// AuthStates, -// AuthUtil, -// amazonQScopes, -// codeWhispererChatScopes, -// codeWhispererCoreScopes, -// } from 'aws-core-vscode/codewhisperer' -// import { -// assertTelemetry, -// getTestWindow, -// SeverityLevel, -// createBuilderIdProfile, -// createSsoProfile, -// createTestAuth, -// captureEventNTimes, -// } from 'aws-core-vscode/test' -// import { Auth, Connection, isAnySsoConnection, isBuilderIdConnection } from 'aws-core-vscode/auth' -// import { globals, vscodeComponent } from 'aws-core-vscode/shared' - -// const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' - -// describe('AuthUtil', async function () { -// let auth: ReturnType -// let authUtil: AuthUtil - -// beforeEach(async function () { -// auth = createTestAuth(globals.globalState) -// authUtil = new AuthUtil(auth) -// }) - -// afterEach(async function () { -// await auth.logout() -// }) - -// it('if there is no valid AwsBuilderID conn, it will create one and use it', async function () { -// getTestWindow().onDidShowQuickPick(async (picker) => { -// await picker.untilReady() -// picker.acceptItem(picker.items[1]) -// }) - -// await authUtil.connectToAwsBuilderId() -// const conn = authUtil.conn -// assert.strictEqual(conn?.type, 'sso') -// assert.strictEqual(conn.label, 'AWS Builder ID') -// assert.deepStrictEqual(conn.scopes, amazonQScopes) -// }) - -// it('if there IS an existing AwsBuilderID conn, it will upgrade the scopes and use it', async function () { -// const existingBuilderId = await auth.createConnection( -// createBuilderIdProfile({ scopes: codeWhispererCoreScopes }) -// ) -// getTestWindow().onDidShowQuickPick(async (picker) => { -// await picker.untilReady() -// picker.acceptItem(picker.items[1]) -// }) - -// await authUtil.connectToAwsBuilderId() - -// const conn = authUtil.conn -// assert.strictEqual(conn?.type, 'sso') -// assert.strictEqual(conn.id, existingBuilderId.id) -// assert.deepStrictEqual(conn.scopes, amazonQScopes) -// }) - -// it('if there is no valid enterprise SSO conn, will create and use one', async function () { -// getTestWindow().onDidShowQuickPick(async (picker) => { -// await picker.untilReady() -// picker.acceptItem(picker.items[1]) -// }) - -// await authUtil.connectToEnterpriseSso(enterpriseSsoStartUrl, 'us-east-1') -// const conn = authUtil.conn -// assert.strictEqual(conn?.type, 'sso') -// assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)') -// }) - -// it('should add scopes + connect to existing IAM Identity Center connection', async function () { -// getTestWindow().onDidShowMessage(async (message) => { -// assert.ok(message.modal) -// message.selectItem('Proceed') -// }) -// const randomScope = 'my:random:scope' -// const ssoConn = await auth.createInvalidSsoConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: [randomScope] }) -// ) - -// // Method under test -// await authUtil.connectToEnterpriseSso(ssoConn.startUrl, 'us-east-1') - -// const cwConn = authUtil.conn -// assert.strictEqual(cwConn?.type, 'sso') -// assert.strictEqual(cwConn.label, 'IAM Identity Center (enterprise)') -// assert.deepStrictEqual(cwConn.scopes, [randomScope, ...amazonQScopes]) -// }) - -// it('reauthenticates an existing BUT invalid Amazon Q IAM Identity Center connection', async function () { -// const ssoConn = await auth.createInvalidSsoConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) -// ) -// await auth.refreshConnectionState(ssoConn) -// assert.strictEqual(auth.getConnectionState(ssoConn), 'invalid') - -// // Method under test -// await authUtil.connectToEnterpriseSso(ssoConn.startUrl, 'us-east-1') - -// const cwConn = authUtil.conn -// assert.strictEqual(cwConn?.type, 'sso') -// assert.strictEqual(cwConn.id, ssoConn.id) -// assert.deepStrictEqual(cwConn.scopes, amazonQScopes) -// assert.strictEqual(auth.getConnectionState(cwConn), 'valid') -// }) - -// it('should show reauthenticate prompt', async function () { -// getTestWindow().onDidShowMessage((m) => { -// if (m.severity === SeverityLevel.Information) { -// m.close() -// } -// }) - -// await authUtil.showReauthenticatePrompt() - -// const warningMessage = getTestWindow().shownMessages.filter((m) => m.severity === SeverityLevel.Information) -// assert.strictEqual(warningMessage.length, 1) -// assert.strictEqual(warningMessage[0].message, `Your Amazon Q connection has expired. Please re-authenticate.`) -// warningMessage[0].close() -// assertTelemetry('toolkit_showNotification', { -// id: 'codeWhispererConnectionExpired', -// result: 'Succeeded', -// source: vscodeComponent, -// }) -// assertTelemetry('toolkit_invokeAction', { -// id: 'codeWhispererConnectionExpired', -// action: 'dismiss', -// result: 'Succeeded', -// source: vscodeComponent, -// }) -// }) - -// it('reauthenticate prompt reauthenticates invalid connection', async function () { -// const conn = await auth.createInvalidSsoConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: codeWhispererChatScopes }) -// ) -// await auth.useConnection(conn) -// getTestWindow().onDidShowMessage((m) => { -// m.selectItem('Re-authenticate') -// }) - -// assert.strictEqual(auth.getConnectionState(conn), 'invalid') - -// await authUtil.showReauthenticatePrompt() - -// assert.strictEqual(authUtil.conn?.type, 'sso') -// assert.strictEqual(auth.getConnectionState(conn), 'valid') -// assertTelemetry('toolkit_showNotification', { -// id: 'codeWhispererConnectionExpired', -// result: 'Succeeded', -// source: vscodeComponent, -// }) -// assertTelemetry('toolkit_invokeAction', { -// id: 'codeWhispererConnectionExpired', -// action: 'connect', -// result: 'Succeeded', -// source: vscodeComponent, -// }) -// }) - -// it('reauthenticates Builder ID connection that already has all scopes', async function () { -// const conn = await auth.createInvalidSsoConnection(createBuilderIdProfile({ scopes: amazonQScopes })) -// await auth.useConnection(conn) - -// // method under test -// await authUtil.reauthenticate() - -// assert.strictEqual(authUtil.conn?.type, 'sso') -// assert.deepStrictEqual(authUtil.conn?.scopes, amazonQScopes) -// assert.strictEqual(auth.getConnectionState(conn), 'valid') -// }) - -// it('reauthenticates IdC connection that already has all scopes', async function () { -// const conn = await auth.createInvalidSsoConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: codeWhispererCoreScopes }) -// ) -// await auth.useConnection(conn) - -// // method under test -// await authUtil.reauthenticate() - -// assert.strictEqual(authUtil.conn?.type, 'sso') -// assert.deepStrictEqual(authUtil.conn?.scopes, amazonQScopes) -// assert.strictEqual(auth.getConnectionState(conn), 'valid') -// }) - -// it('reauthenticate adds missing Builder ID scopes', async function () { -// const conn = await auth.createInvalidSsoConnection(createBuilderIdProfile({ scopes: codeWhispererCoreScopes })) -// await auth.useConnection(conn) - -// // method under test -// await authUtil.reauthenticate() - -// assert.strictEqual(authUtil.conn?.type, 'sso') -// assert.deepStrictEqual(authUtil.conn?.scopes, amazonQScopes) -// assert.strictEqual(auth.getConnectionState(conn), 'valid') -// }) - -// it('reauthenticate adds missing Amazon Q IdC scopes', async function () { -// const conn = await auth.createInvalidSsoConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: codeWhispererCoreScopes }) -// ) -// await auth.useConnection(conn) - -// // method under test -// await authUtil.reauthenticate() - -// assert.strictEqual(authUtil.conn?.type, 'sso') -// assert.deepStrictEqual(authUtil.conn?.scopes, amazonQScopes) -// assert.strictEqual(auth.getConnectionState(conn), 'valid') -// }) - -// it('CodeWhisperer uses fallback connection when switching to an unsupported connection', async function () { -// const supportedConn = await auth.createConnection(createBuilderIdProfile({ scopes: codeWhispererChatScopes })) -// const unsupportedConn = await auth.createConnection(createSsoProfile()) - -// await auth.useConnection(supportedConn) -// assert.ok(authUtil.isConnected()) -// assert.strictEqual(auth.activeConnection?.id, authUtil.conn?.id) - -// // Switch to unsupported connection -// const cwAuthUpdatedConnection = captureEventNTimes(authUtil.secondaryAuth.onDidChangeActiveConnection, 2) -// await auth.useConnection(unsupportedConn) -// // - This is triggered when the main Auth connection is switched -// // - This is triggered by registerAuthListener() when it saves the previous active connection as a fallback. -// await cwAuthUpdatedConnection - -// // TODO in a refactor see if we can simplify multiple multiple triggers on the same event. -// assert.ok(authUtil.isConnected()) -// assert.ok(authUtil.isUsingSavedConnection) -// assert.notStrictEqual(auth.activeConnection?.id, authUtil.conn?.id) -// assert.strictEqual(authUtil.conn?.type, 'sso') -// assert.deepStrictEqual(authUtil.conn?.scopes, codeWhispererChatScopes) -// }) - -// it('does not prompt to sign out of duplicate builder ID connections', async function () { -// await authUtil.connectToAwsBuilderId() -// await authUtil.connectToAwsBuilderId() -// assert.ok(authUtil.isConnected()) - -// const ssoConnectionIds = new Set(auth.activeConnectionEvents.emits.filter(isAnySsoConnection).map((c) => c.id)) -// assert.strictEqual(ssoConnectionIds.size, 1, 'Expected exactly 1 unique SSO connection id') -// assert.strictEqual((await auth.listConnections()).filter(isAnySsoConnection).length, 1) -// }) - -// it('automatically upgrades connections if they do not have the required scopes', async function () { -// const upgradeableConn = await auth.createConnection(createBuilderIdProfile()) -// await auth.useConnection(upgradeableConn) -// assert.strictEqual(authUtil.isConnected(), false) - -// await authUtil.connectToAwsBuilderId() -// assert.ok(authUtil.isConnected()) -// assert.ok(authUtil.isConnectionValid()) -// assert.ok(isBuilderIdConnection(authUtil.conn)) -// assert.strictEqual(authUtil.conn?.id, upgradeableConn.id) -// assert.strictEqual(authUtil.conn.startUrl, upgradeableConn.startUrl) -// assert.strictEqual(authUtil.conn.ssoRegion, upgradeableConn.ssoRegion) -// assert.deepStrictEqual(authUtil.conn.scopes, amazonQScopes) -// assert.strictEqual((await auth.listConnections()).filter(isAnySsoConnection).length, 1) -// }) - -// it('test reformatStartUrl should remove trailing slash and hash', function () { -// const expected = 'https://view.awsapps.com/start' -// assert.strictEqual(authUtil.reformatStartUrl(expected + '/'), expected) -// assert.strictEqual(authUtil.reformatStartUrl(undefined), undefined) -// assert.strictEqual(authUtil.reformatStartUrl(expected + '/#'), expected) -// assert.strictEqual(authUtil.reformatStartUrl(expected + '#/'), expected) -// assert.strictEqual(authUtil.reformatStartUrl(expected + '/#/'), expected) -// assert.strictEqual(authUtil.reformatStartUrl(expected + '####'), expected) -// }) - -// it(`clearExtraConnections()`, async function () { -// const conn1 = await auth.createConnection(createBuilderIdProfile()) -// const conn2 = await auth.createConnection(createSsoProfile({ startUrl: enterpriseSsoStartUrl })) -// const conn3 = await auth.createConnection(createSsoProfile({ startUrl: enterpriseSsoStartUrl + 1 })) -// // validate listConnections shows all connections -// assert.deepStrictEqual( -// (await authUtil.auth.listConnections()).map((conn) => conn.id).sort((a, b) => a.localeCompare(b)), -// [conn1, conn2, conn3].map((conn) => conn.id).sort((a, b) => a.localeCompare(b)) -// ) -// await authUtil.secondaryAuth.useNewConnection(conn3) - -// await authUtil.clearExtraConnections() // method under test - -// // Only the conn that AuthUtil is using is remaining -// assert.deepStrictEqual( -// (await authUtil.auth.listConnections()).map((conn) => conn.id), -// [conn3.id] -// ) -// }) -// }) - -// describe('getChatAuthState()', function () { -// let auth: ReturnType -// let authUtil: AuthUtil -// let laterDate: Date - -// beforeEach(async function () { -// auth = createTestAuth(globals.globalState) -// authUtil = new AuthUtil(auth) - -// laterDate = new Date(Date.now() + 10_000_000) -// }) - -// afterEach(async function () { -// await auth.logout() -// }) - -// it('indicates nothing connected when no auth connection exists', async function () { -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererChat: AuthStates.disconnected, -// codewhispererCore: AuthStates.disconnected, -// amazonQ: AuthStates.disconnected, -// }) -// }) - -// /** Affects {@link Auth.refreshConnectionState} */ -// function createToken(conn: Connection) { -// auth.getTestTokenProvider(conn).getToken.resolves({ accessToken: 'myAccessToken', expiresAt: laterDate }) -// } - -// describe('Builder ID', function () { -// it('indicates only CodeWhisperer core is connected when only CW core scopes are set', async function () { -// const conn = await auth.createConnection(createBuilderIdProfile({ scopes: codeWhispererCoreScopes })) -// createToken(conn) -// await auth.useConnection(conn) - -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererCore: AuthStates.connected, -// codewhispererChat: AuthStates.expired, -// amazonQ: AuthStates.expired, -// }) -// }) - -// it('indicates all SUPPORTED features connected when all scopes are set', async function () { -// const conn = await auth.createConnection(createBuilderIdProfile({ scopes: amazonQScopes })) -// createToken(conn) -// await auth.useConnection(conn) - -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererCore: AuthStates.connected, -// codewhispererChat: AuthStates.connected, -// amazonQ: AuthStates.connected, -// }) -// }) - -// it('indicates all SUPPORTED features expired when connection is invalid', async function () { -// const conn = await auth.createInvalidSsoConnection( -// createBuilderIdProfile({ scopes: codeWhispererChatScopes }) -// ) -// await auth.useConnection(conn) - -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererCore: AuthStates.expired, -// codewhispererChat: AuthStates.expired, -// amazonQ: AuthStates.expired, -// }) -// }) -// }) - -// describe('Identity Center', function () { -// it('indicates only CW core is connected when only CW core scopes are set', async function () { -// const conn = await auth.createConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: codeWhispererCoreScopes }) -// ) -// createToken(conn) -// await auth.useConnection(conn) - -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererCore: AuthStates.pendingProfileSelection, -// codewhispererChat: AuthStates.expired, -// amazonQ: AuthStates.expired, -// }) -// }) - -// it('indicates all features connected when all scopes are set', async function () { -// const conn = await auth.createConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) -// ) -// createToken(conn) -// await auth.useConnection(conn) - -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererCore: AuthStates.pendingProfileSelection, -// codewhispererChat: AuthStates.pendingProfileSelection, -// amazonQ: AuthStates.pendingProfileSelection, -// }) -// }) - -// it('indicates all features expired when connection is invalid', async function () { -// const conn = await auth.createInvalidSsoConnection( -// createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) -// ) -// await auth.useConnection(conn) - -// const result = await authUtil.getChatAuthState() -// assert.deepStrictEqual(result, { -// codewhispererCore: AuthStates.expired, -// codewhispererChat: AuthStates.expired, -// amazonQ: AuthStates.expired, -// }) -// }) -// }) -// }) +import assert from 'assert' +import * as sinon from 'sinon' +import { AuthUtil } from 'aws-core-vscode/codewhisperer' +import { createTestAuthUtil } from 'aws-core-vscode/test' +import { constants } from 'aws-core-vscode/auth' +import { auth2 } from 'aws-core-vscode/auth' + +describe('AuthUtil', async function () { + let auth: any + + beforeEach(async function () { + await createTestAuthUtil() + auth = AuthUtil.instance + }) + + afterEach(async function () { + sinon.restore() + }) + + describe('Auth state', function () { + it('login with BuilderId', async function () { + await auth.login(constants.builderIdStartUrl, constants.builderIdRegion) + assert.ok(auth.isConnected()) + assert.ok(auth.isBuilderIdConnection()) + }) + + it('login with IDC', async function () { + await auth.login('https://example.awsapps.com/start', 'us-east-1') + assert.ok(auth.isConnected()) + assert.ok(auth.isIdcConnection()) + }) + + it('identifies internal users', async function () { + await auth.login(constants.internalStartUrl, 'us-east-1') + assert.ok(auth.isInternalAmazonUser()) + }) + + it('identifies SSO session', function () { + ;(auth as any).session = { loginType: auth2.LoginTypes.SSO } + assert.strictEqual(auth.isSsoSession(), true) + }) + + it('identifies non-SSO session', function () { + ;(auth as any).session = { loginType: auth2.LoginTypes.IAM } + assert.strictEqual(auth.isSsoSession(), false) + }) + }) + + describe('Token management', function () { + it('can get token when connected with SSO', async function () { + await auth.login(constants.builderIdStartUrl, constants.builderIdRegion) + const token = await auth.getToken() + assert.ok(token) + }) + + it('throws when getting token without SSO connection', async function () { + sinon.stub(AuthUtil.instance, 'isSsoSession').returns(false) + await assert.rejects(async () => await auth.getToken()) + }) + }) + + describe('getTelemetryMetadata', function () { + it('returns valid metadata for BuilderId connection', async function () { + await auth.login(constants.builderIdStartUrl, constants.builderIdRegion) + const metadata = await auth.getTelemetryMetadata() + assert.strictEqual(metadata.credentialSourceId, 'awsId') + assert.strictEqual(metadata.credentialStartUrl, constants.builderIdStartUrl) + }) + + it('returns valid metadata for IDC connection', async function () { + await auth.login('https://example.awsapps.com/start', 'us-east-1') + const metadata = await auth.getTelemetryMetadata() + assert.strictEqual(metadata.credentialSourceId, 'iamIdentityCenter') + assert.strictEqual(metadata.credentialStartUrl, 'https://example.awsapps.com/start') + }) + + it('returns undefined metadata when not connected', async function () { + await auth.logout() + const metadata = await auth.getTelemetryMetadata() + assert.strictEqual(metadata.id, 'undefined') + }) + }) + + describe('getAuthFormIds', function () { + it('returns empty array when not connected', async function () { + await auth.logout() + const forms = await auth.getAuthFormIds() + assert.deepStrictEqual(forms, []) + }) + + it('returns BuilderId forms when using BuilderId', async function () { + await auth.login(constants.builderIdStartUrl, constants.builderIdRegion) + const forms = await auth.getAuthFormIds() + assert.deepStrictEqual(forms, ['builderIdCodeWhisperer']) + }) + + it('returns IDC forms when using IDC without SSO account access', async function () { + const session = (auth as any).session + sinon.stub(session, 'getProfile').resolves({ + ssoSession: { + settings: { + sso_registration_scopes: ['codewhisperer:*'], + }, + }, + }) + + await auth.login('https://example.awsapps.com/start', 'us-east-1') + const forms = await auth.getAuthFormIds() + assert.deepStrictEqual(forms, ['identityCenterCodeWhisperer']) + }) + + it('returns IDC forms with explorer when using IDC with SSO account access', async function () { + const session = (auth as any).session + sinon.stub(session, 'getProfile').resolves({ + ssoSession: { + settings: { + sso_registration_scopes: ['codewhisperer:*', 'sso:account:access'], + }, + }, + }) + + await auth.login('https://example.awsapps.com/start', 'us-east-1') + const forms = await auth.getAuthFormIds() + assert.deepStrictEqual(forms.sort(), ['identityCenterCodeWhisperer', 'identityCenterExplorer'].sort()) + }) + + it('returns credentials form for IAM credentials', async function () { + sinon.stub(auth, 'isSsoSession').returns(false) + sinon.stub(auth, 'isConnected').returns(true) + + const forms = await auth.getAuthFormIds() + assert.deepStrictEqual(forms, ['credentials']) + }) + }) + + describe('stateChangeHandler', function () { + let mockLspAuth: any + let regionProfileManager: any + + beforeEach(function () { + mockLspAuth = (auth as any).lspAuth + regionProfileManager = (auth as any).regionProfileManager + }) + + it('updates bearer token when state is refreshed', async function () { + await auth.login(constants.builderIdStartUrl, 'us-east-1') + + await (auth as any).stateChangeHandler({ state: 'refreshed' }) + + assert.ok(mockLspAuth.updateBearerToken.called) + assert.strictEqual(mockLspAuth.updateBearerToken.firstCall.args[0].data, 'fake-data') + }) + + it('cleans up when connection expires', async function () { + await auth.login(constants.builderIdStartUrl, 'us-east-1') + + await (auth as any).stateChangeHandler({ state: 'expired' }) + + assert.ok(mockLspAuth.deleteBearerToken.called) + }) + + it('deletes bearer token when disconnected', async function () { + await (auth as any).stateChangeHandler({ state: 'notConnected' }) + + assert.ok(mockLspAuth.deleteBearerToken.called) + }) + + it('updates bearer token and restores profile on reconnection', async function () { + const restoreProfileSelectionSpy = sinon.spy(regionProfileManager, 'restoreProfileSelection') + + await auth.login('https://example.awsapps.com/start', 'us-east-1') + + await (auth as any).stateChangeHandler({ state: 'connected' }) + + assert.ok(mockLspAuth.updateBearerToken.called) + assert.ok(restoreProfileSelectionSpy.called) + }) + + it('clears region profile cache and invalidates profile on IDC connection expiration', async function () { + const invalidateProfileSpy = sinon.spy(regionProfileManager, 'invalidateProfile') + const clearCacheSpy = sinon.spy(regionProfileManager, 'clearCache') + + await auth.login('https://example.awsapps.com/start', 'us-east-1') + + await (auth as any).stateChangeHandler({ state: 'expired' }) + + assert.ok(invalidateProfileSpy.called) + assert.ok(clearCacheSpy.called) + }) + }) +}) diff --git a/packages/amazonq/test/unit/codewhisperer/util/diagnosticsUtil.test.ts b/packages/amazonq/test/unit/codewhisperer/util/diagnosticsUtil.test.ts new file mode 100644 index 00000000000..6c33d61ba87 --- /dev/null +++ b/packages/amazonq/test/unit/codewhisperer/util/diagnosticsUtil.test.ts @@ -0,0 +1,92 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as assert from 'assert' +import * as vscode from 'vscode' +import { getDiagnosticsType, getDiagnosticsDifferences } from 'aws-core-vscode/codewhisperer' +describe('diagnosticsUtil', function () { + describe('getDiagnosticsType', function () { + it('should identify SYNTAX_ERROR correctly', function () { + assert.strictEqual(getDiagnosticsType('Expected semicolon'), 'SYNTAX_ERROR') + assert.strictEqual(getDiagnosticsType('Incorrect indent level'), 'SYNTAX_ERROR') + assert.strictEqual(getDiagnosticsType('Syntax error in line 5'), 'SYNTAX_ERROR') + }) + + it('should identify TYPE_ERROR correctly', function () { + assert.strictEqual(getDiagnosticsType('Type mismatch'), 'TYPE_ERROR') + assert.strictEqual(getDiagnosticsType('Invalid type cast'), 'TYPE_ERROR') + }) + + it('should identify REFERENCE_ERROR correctly', function () { + assert.strictEqual(getDiagnosticsType('Variable is undefined'), 'REFERENCE_ERROR') + assert.strictEqual(getDiagnosticsType('Variable not defined'), 'REFERENCE_ERROR') + assert.strictEqual(getDiagnosticsType('Reference error occurred'), 'REFERENCE_ERROR') + }) + + it('should identify BEST_PRACTICE correctly', function () { + assert.strictEqual(getDiagnosticsType('Using deprecated method'), 'BEST_PRACTICE') + assert.strictEqual(getDiagnosticsType('Variable is unused'), 'BEST_PRACTICE') + assert.strictEqual(getDiagnosticsType('Variable not initialized'), 'BEST_PRACTICE') + }) + + it('should identify SECURITY correctly', function () { + assert.strictEqual(getDiagnosticsType('Potential security vulnerability'), 'SECURITY') + assert.strictEqual(getDiagnosticsType('Security risk detected'), 'SECURITY') + }) + + it('should return OTHER for unrecognized messages', function () { + assert.strictEqual(getDiagnosticsType('Random message'), 'OTHER') + assert.strictEqual(getDiagnosticsType(''), 'OTHER') + }) + }) + + describe('getDiagnosticsDifferences', function () { + const createDiagnostic = (message: string): vscode.Diagnostic => { + return { + message, + severity: vscode.DiagnosticSeverity.Error, + range: new vscode.Range(0, 0, 0, 1), + source: 'test', + } + } + + it('should return empty arrays when both inputs are undefined', function () { + const result = getDiagnosticsDifferences(undefined, undefined) + assert.deepStrictEqual(result, { added: [], removed: [] }) + }) + + it('should return empty arrays when filepaths are different', function () { + const oldDiagnostics = { + filepath: '/path/to/file1', + diagnostics: [createDiagnostic('error1')], + } + const newDiagnostics = { + filepath: '/path/to/file2', + diagnostics: [createDiagnostic('error1')], + } + const result = getDiagnosticsDifferences(oldDiagnostics, newDiagnostics) + assert.deepStrictEqual(result, { added: [], removed: [] }) + }) + + it('should correctly identify added and removed diagnostics', function () { + const diagnostic1 = createDiagnostic('error1') + const diagnostic2 = createDiagnostic('error2') + const diagnostic3 = createDiagnostic('error3') + + const oldDiagnostics = { + filepath: '/path/to/file', + diagnostics: [diagnostic1, diagnostic2], + } + const newDiagnostics = { + filepath: '/path/to/file', + diagnostics: [diagnostic2, diagnostic3], + } + + const result = getDiagnosticsDifferences(oldDiagnostics, newDiagnostics) + assert.deepStrictEqual(result.added, [diagnostic3]) + assert.deepStrictEqual(result.removed, [diagnostic1]) + }) + }) +}) diff --git a/packages/amazonq/test/unit/codewhisperer/util/editorContext.test.ts b/packages/amazonq/test/unit/codewhisperer/util/editorContext.test.ts index d5085e4db0c..f8265a4fa86 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/editorContext.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/editorContext.test.ts @@ -6,6 +6,7 @@ import assert from 'assert' import * as codewhispererClient from 'aws-core-vscode/codewhisperer' import * as EditorContext from 'aws-core-vscode/codewhisperer' import { + createMockDocument, createMockTextEditor, createMockClientRequest, resetCodeWhispererGlobalVariables, @@ -15,6 +16,27 @@ import { } from 'aws-core-vscode/test' import { globals } from 'aws-core-vscode/shared' import { GenerateCompletionsRequest } from 'aws-core-vscode/codewhisperer' +import * as vscode from 'vscode' + +export function createNotebookCell( + document: vscode.TextDocument = createMockDocument('def example():\n return "test"'), + kind: vscode.NotebookCellKind = vscode.NotebookCellKind.Code, + notebook: vscode.NotebookDocument = {} as any, + index: number = 0, + outputs: vscode.NotebookCellOutput[] = [], + metadata: { readonly [key: string]: any } = {}, + executionSummary?: vscode.NotebookCellExecutionSummary +): vscode.NotebookCell { + return { + document, + kind, + notebook, + index, + outputs, + metadata, + executionSummary, + } +} describe('editorContext', function () { let telemetryEnabledDefault: boolean @@ -63,6 +85,44 @@ describe('editorContext', function () { } assert.deepStrictEqual(actual, expected) }) + + it('in a notebook, includes context from other cells', async function () { + const cells: vscode.NotebookCellData[] = [ + new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, 'Previous cell', 'python'), + new vscode.NotebookCellData( + vscode.NotebookCellKind.Code, + 'import numpy as np\nimport pandas as pd\n\ndef analyze_data(df):\n # Current cell with cursor here', + 'python' + ), + new vscode.NotebookCellData( + vscode.NotebookCellKind.Code, + '# Process the data\nresult = analyze_data(df)\nprint(result)', + 'python' + ), + ] + + const document = await vscode.workspace.openNotebookDocument( + 'jupyter-notebook', + new vscode.NotebookData(cells) + ) + const editor: any = { + document: document.cellAt(1).document, + selection: { active: new vscode.Position(4, 13) }, + } + + const actual = EditorContext.extractContextForCodeWhisperer(editor) + const expected: codewhispererClient.FileContext = { + filename: 'Untitled-1.py', + programmingLanguage: { + languageName: 'python', + }, + leftFileContent: + '# Previous cell\nimport numpy as np\nimport pandas as pd\n\ndef analyze_data(df):\n # Current', + rightFileContent: + ' cell with cursor here\n# Process the data\nresult = analyze_data(df)\nprint(result)\n', + } + assert.deepStrictEqual(actual, expected) + }) }) describe('getFileName', function () { @@ -115,6 +175,165 @@ describe('editorContext', function () { }) }) + describe('getNotebookCellContext', function () { + it('Should return cell text for python code cells when language is python', function () { + const mockCodeCell = createNotebookCell(createMockDocument('def example():\n return "test"')) + const result = EditorContext.getNotebookCellContext(mockCodeCell, 'python') + assert.strictEqual(result, 'def example():\n return "test"') + }) + + it('Should return java comments for python code cells when language is java', function () { + const mockCodeCell = createNotebookCell(createMockDocument('def example():\n return "test"')) + const result = EditorContext.getNotebookCellContext(mockCodeCell, 'java') + assert.strictEqual(result, '// def example():\n// return "test"') + }) + + it('Should return python comments for java code cells when language is python', function () { + const mockCodeCell = createNotebookCell(createMockDocument('println(1 + 1);', 'somefile.ipynb', 'java')) + const result = EditorContext.getNotebookCellContext(mockCodeCell, 'python') + assert.strictEqual(result, '# println(1 + 1);') + }) + + it('Should add python comment prefixes for markdown cells when language is python', function () { + const mockMarkdownCell = createNotebookCell( + createMockDocument('# Heading\nThis is a markdown cell'), + vscode.NotebookCellKind.Markup + ) + const result = EditorContext.getNotebookCellContext(mockMarkdownCell, 'python') + assert.strictEqual(result, '# # Heading\n# This is a markdown cell') + }) + + it('Should add java comment prefixes for markdown cells when language is java', function () { + const mockMarkdownCell = createNotebookCell( + createMockDocument('# Heading\nThis is a markdown cell'), + vscode.NotebookCellKind.Markup + ) + const result = EditorContext.getNotebookCellContext(mockMarkdownCell, 'java') + assert.strictEqual(result, '// # Heading\n// This is a markdown cell') + }) + }) + + describe('getNotebookCellsSliceContext', function () { + it('Should extract content from cells in reverse order up to maxLength from prefix cells', function () { + const mockCells = [ + createNotebookCell(createMockDocument('First cell content')), + createNotebookCell(createMockDocument('Second cell content')), + createNotebookCell(createMockDocument('Third cell content')), + ] + + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'python', false) + assert.strictEqual(result, 'First cell content\nSecond cell content\nThird cell content\n') + }) + + it('Should extract content from cells in reverse order up to maxLength from suffix cells', function () { + const mockCells = [ + createNotebookCell(createMockDocument('First cell content')), + createNotebookCell(createMockDocument('Second cell content')), + createNotebookCell(createMockDocument('Third cell content')), + ] + + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'python', true) + assert.strictEqual(result, 'First cell content\nSecond cell content\nThird cell content\n') + }) + + it('Should respect maxLength parameter from prefix cells', function () { + const mockCells = [ + createNotebookCell(createMockDocument('First')), + createNotebookCell(createMockDocument('Second')), + createNotebookCell(createMockDocument('Third')), + createNotebookCell(createMockDocument('Fourth')), + ] + // Should only include part of second cell and the last two cells + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 15, 'python', false) + assert.strictEqual(result, 'd\nThird\nFourth\n') + }) + + it('Should respect maxLength parameter from suffix cells', function () { + const mockCells = [ + createNotebookCell(createMockDocument('First')), + createNotebookCell(createMockDocument('Second')), + createNotebookCell(createMockDocument('Third')), + createNotebookCell(createMockDocument('Fourth')), + ] + + // Should only include first cell and part of second cell + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 15, 'python', true) + assert.strictEqual(result, 'First\nSecond\nTh') + }) + + it('Should handle empty cells array from prefix cells', function () { + const result = EditorContext.getNotebookCellsSliceContext([], 100, 'python', false) + assert.strictEqual(result, '') + }) + + it('Should handle empty cells array from suffix cells', function () { + const result = EditorContext.getNotebookCellsSliceContext([], 100, 'python', true) + assert.strictEqual(result, '') + }) + + it('Should add python comments to markdown prefix cells', function () { + const mockCells = [ + createNotebookCell(createMockDocument('# Heading\nThis is markdown'), vscode.NotebookCellKind.Markup), + createNotebookCell(createMockDocument('def example():\n return "test"')), + ] + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'python', false) + assert.strictEqual(result, '# # Heading\n# This is markdown\ndef example():\n return "test"\n') + }) + + it('Should add python comments to markdown suffix cells', function () { + const mockCells = [ + createNotebookCell(createMockDocument('# Heading\nThis is markdown'), vscode.NotebookCellKind.Markup), + createNotebookCell(createMockDocument('def example():\n return "test"')), + ] + + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'python', true) + assert.strictEqual(result, '# # Heading\n# This is markdown\ndef example():\n return "test"\n') + }) + + it('Should add java comments to markdown and python prefix cells when language is java', function () { + const mockCells = [ + createNotebookCell(createMockDocument('# Heading\nThis is markdown'), vscode.NotebookCellKind.Markup), + createNotebookCell(createMockDocument('def example():\n return "test"')), + ] + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'java', false) + assert.strictEqual(result, '// # Heading\n// This is markdown\n// def example():\n// return "test"\n') + }) + + it('Should add java comments to markdown and python suffix cells when language is java', function () { + const mockCells = [ + createNotebookCell(createMockDocument('# Heading\nThis is markdown'), vscode.NotebookCellKind.Markup), + createNotebookCell(createMockDocument('println(1 + 1);', 'somefile.ipynb', 'java')), + ] + + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'java', true) + assert.strictEqual(result, '// # Heading\n// This is markdown\nprintln(1 + 1);\n') + }) + + it('Should handle code prefix cells with different languages', function () { + const mockCells = [ + createNotebookCell( + createMockDocument('println(1 + 1);', 'somefile.ipynb', 'java'), + vscode.NotebookCellKind.Code + ), + createNotebookCell(createMockDocument('def example():\n return "test"')), + ] + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'python', false) + assert.strictEqual(result, '# println(1 + 1);\ndef example():\n return "test"\n') + }) + + it('Should handle code suffix cells with different languages', function () { + const mockCells = [ + createNotebookCell( + createMockDocument('println(1 + 1);', 'somefile.ipynb', 'java'), + vscode.NotebookCellKind.Code + ), + createNotebookCell(createMockDocument('def example():\n return "test"')), + ] + const result = EditorContext.getNotebookCellsSliceContext(mockCells, 100, 'python', true) + assert.strictEqual(result, '# println(1 + 1);\ndef example():\n return "test"\n') + }) + }) + describe('validateRequest', function () { it('Should return false if request filename.length is invalid', function () { const req = createMockClientRequest() diff --git a/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts b/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts index 9cf61920861..a5cc430a5a9 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts @@ -4,13 +4,18 @@ */ import assert from 'assert' -import { resetCodeWhispererGlobalVariables, toTextDocument } from 'aws-core-vscode/test' +import { resetCodeWhispererGlobalVariables, TestFolder, toTextDocument } from 'aws-core-vscode/test' import { runtimeLanguageContext, RuntimeLanguageContext, PlatformLanguageId } from 'aws-core-vscode/codewhisperer' import * as codewhispererClient from 'aws-core-vscode/codewhisperer' import { CodewhispererLanguage } from 'aws-core-vscode/shared' describe('runtimeLanguageContext', function () { const languageContext = new RuntimeLanguageContext() + let tempFolder: TestFolder + + before(async function () { + tempFolder = await TestFolder.create() + }) describe('test isLanguageSupported', function () { const cases: [string, boolean][] = [ @@ -104,13 +109,12 @@ describe('runtimeLanguageContext', function () { ['helloUnknown', false], ['helloFoo.foo', false], ] - for (const tuple of cases) { const fileName = tuple[0] const expected = tuple[1] it(`pass document ${fileName} as argument should first try determine by languageId then file extensions`, async function () { - const doc = await toTextDocument('', fileName) + const doc = await toTextDocument('', fileName, tempFolder.path) const actual = languageContext.isLanguageSupported(doc) assert.strictEqual(actual, expected) }) @@ -329,6 +333,40 @@ describe('runtimeLanguageContext', function () { } }) + describe('getSingleLineCommentPrefix', function () { + it('should return the correct comment prefix for supported languages', function () { + assert.strictEqual(languageContext.getSingleLineCommentPrefix('java'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('javascript'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('jsonc'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('kotlin'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('lua'), '-- ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('python'), '# ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('ruby'), '# ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('sql'), '-- ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('tf'), '# ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('typescript'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('vue'), '') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('yaml'), '# ') + }) + + it('should normalize language ID before getting comment prefix', function () { + assert.strictEqual(languageContext.getSingleLineCommentPrefix('hcl'), '# ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('javascriptreact'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('shellscript'), '# ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('typescriptreact'), '// ') + assert.strictEqual(languageContext.getSingleLineCommentPrefix('yml'), '# ') + }) + + it('should return empty string for unsupported languages', function () { + assert.strictEqual(languageContext.getSingleLineCommentPrefix('nonexistent'), '') + assert.strictEqual(languageContext.getSingleLineCommentPrefix(undefined), '') + }) + + it('should return empty string for plaintext', function () { + assert.strictEqual(languageContext.getSingleLineCommentPrefix('plaintext'), '') + }) + }) + // for now we will only jsx mapped to javascript, tsx mapped to typescript, all other language should remain the same describe('test covertCwsprRequest', function () { const leftFileContent = 'left' diff --git a/packages/amazonq/test/unit/codewhisperer/util/showSsoPrompt.test.ts b/packages/amazonq/test/unit/codewhisperer/util/showSsoPrompt.test.ts index 921b40f6aea..1d67db60efc 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/showSsoPrompt.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/showSsoPrompt.test.ts @@ -7,8 +7,9 @@ import * as vscode from 'vscode' import assert from 'assert' import * as sinon from 'sinon' import { resetCodeWhispererGlobalVariables } from 'aws-core-vscode/test' -import { assertTelemetryCurried, getTestWindow, getTestLogger } from 'aws-core-vscode/test' +import { assertTelemetryCurried, getTestWindow } from 'aws-core-vscode/test' import { AuthUtil, awsIdSignIn, showCodeWhispererConnectionPrompt } from 'aws-core-vscode/codewhisperer' +import { SsoAccessTokenProvider, constants } from 'aws-core-vscode/auth' describe('showConnectionPrompt', function () { let isBuilderIdConnection: sinon.SinonStub @@ -17,6 +18,9 @@ describe('showConnectionPrompt', function () { await resetCodeWhispererGlobalVariables() isBuilderIdConnection = sinon.stub(AuthUtil.instance, 'isBuilderIdConnection') isBuilderIdConnection.resolves() + + // Stub useDeviceFlow so we always use DeviceFlow for auth + sinon.stub(SsoAccessTokenProvider, 'useDeviceFlow').returns(true) }) afterEach(function () { @@ -24,6 +28,8 @@ describe('showConnectionPrompt', function () { }) it('can select connect to AwsBuilderId', async function () { + sinon.stub(AuthUtil.instance, 'login').resolves() + getTestWindow().onDidShowQuickPick(async (picker) => { await picker.untilReady() picker.acceptItem(picker.items[0]) @@ -36,12 +42,13 @@ describe('showConnectionPrompt', function () { assert.ok(isBuilderIdConnection) }) - it('connectToAwsBuilderId logs that AWS ID sign in was selected', async function () { + it('connectToAwsBuilderId calls AuthUtil login with builderIdStartUrl', async function () { sinon.stub(vscode.commands, 'executeCommand') + const loginStub = sinon.stub(AuthUtil.instance, 'login').resolves() await awsIdSignIn() - const loggedEntries = getTestLogger().getLoggedEntries() - assert.ok(loggedEntries.find((entry) => entry === 'selected AWS ID sign in')) + assert.strictEqual(loginStub.called, true) + assert.strictEqual(loginStub.firstCall.args[0], constants.builderIdStartUrl) }) }) diff --git a/packages/core/package.json b/packages/core/package.json index 8df2aa2b653..0ed5e368121 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,6 +13,7 @@ "./web": "./dist/src/extensionWeb.js", "./webShared": "./dist/src/extensionWebShared.js", "./amazonq": "./dist/src/amazonq/index.js", + "./amazonq/node": "./dist/src/amazonq/indexNode.js", "./codewhisperer": "./dist/src/codewhisperer/index.js", "./codewhisperer/node": "./dist/src/codewhisperer/indexNode.js", "./shared": "./dist/src/shared/index.js", @@ -440,9 +441,10 @@ }, "devDependencies": { "@aws-sdk/types": "^3.13.1", - "@aws/chat-client-ui-types": "^0.1.12", - "@aws/language-server-runtimes": "^0.2.58", - "@aws/language-server-runtimes-types": "^0.1.13", + "@aws/chat-client": "^0.1.4", + "@aws/chat-client-ui-types": "^0.1.24", + "@aws/language-server-runtimes": "^0.2.70", + "@aws/language-server-runtimes-types": "^0.1.26", "@cspotcode/source-map-support": "^0.8.1", "@sinonjs/fake-timers": "^10.0.2", "@types/adm-zip": "^0.4.34", @@ -524,7 +526,7 @@ "@aws-sdk/s3-request-presigner": "<3.731.0", "@aws-sdk/smithy-client": "<3.731.0", "@aws-sdk/util-arn-parser": "<3.731.0", - "@aws/mynah-ui": "^4.30.1", + "@aws/mynah-ui": "^4.30.3", "@gerhobbelt/gitignore-parser": "^0.2.0-9", "@iarna/toml": "^2.2.5", "@smithy/fetch-http-handler": "^5.0.1", diff --git a/packages/core/package.nls.json b/packages/core/package.nls.json index 9c6ebe66067..81f56a32c57 100644 --- a/packages/core/package.nls.json +++ b/packages/core/package.nls.json @@ -93,7 +93,10 @@ "AWS.configuration.description.amazonq.workspaceIndex": "When you add @workspace to your question in Amazon Q chat, Amazon Q will index your workspace files locally to use as context for its response. Extra CPU usage is expected while indexing a workspace. This will not impact Amazon Q features or your IDE, but you may manage CPU usage by setting the number of local threads in 'Local Workspace Index Threads'.", "AWS.configuration.description.amazonq.workspaceIndexWorkerThreads": "Number of worker threads of Amazon Q local index process. '0' will use the system default worker threads for balance performance. You may increase this number to more quickly index your workspace, but only up to your hardware's number of CPU cores. Please restart VS Code or reload the VS Code window after changing worker threads.", "AWS.configuration.description.amazonq.workspaceIndexUseGPU": "Enable GPU to help index your local workspace files. Only applies to Linux and Windows.", - "AWS.configuration.description.amazonq.workspaceIndexMaxSize": "The maximum size of local workspace files to be indexed in MB", + "AWS.configuration.description.amazonq.workspaceIndexMaxSize": "The maximum size of local workspace to be indexed in MB", + "AWS.configuration.description.amazonq.workspaceIndexMaxFileSize": "The maximum size of local workspace files to be indexed in MB", + "AWS.configuration.description.amazonq.workspaceIndexIgnoreFilePatterns": "File patterns to ignore when indexing your workspace files", + "AWS.configuration.description.amazonq.workspaceIndexCacheDirPath": "The path to the directory that contains the cache of the index of your workspace files", "AWS.configuration.description.amazonq.ignoredSecurityIssues": "Specifies a list of code issue identifiers that Amazon Q should ignore when reviewing your workspace. Each item in the array should be a unique string identifier for a specific code issue. This allows you to suppress notifications for known issues that you've assessed and determined to be false positives or not applicable to your project. Use this setting with caution, as it may cause you to miss important security alerts.", "AWS.command.apig.copyUrl": "Copy URL", "AWS.command.apig.invokeRemoteRestApi": "Invoke in the cloud", diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index 64e1254c21a..97c828e6f9e 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -11,7 +11,7 @@ import { amazonQDiffScheme, amazonQTabSuffix } from '../../../shared/constants' import { disposeOnEditorClose } from '../../../shared/utilities/editorUtilities' import { applyChanges, - createTempFileForDiff, + createTempUrisForDiff, getIndentedCode, getSelectionFromRange, } from '../../../shared/utilities/textDocumentUtilities' @@ -19,6 +19,12 @@ import { ToolkitError, getErrorMsg } from '../../../shared/errors' import fs from '../../../shared/fs/fs' import { extractFileAndCodeSelectionFromMessage } from '../../../shared/utilities/textUtilities' import { UserWrittenCodeTracker } from '../../../codewhisperer/tracker/userWrittenCodeTracker' +import { CWCTelemetryHelper } from '../../../codewhispererChat/controllers/chat/telemetryHelper' +import type { ViewDiff } from '../../../codewhispererChat/controllers/chat/model' +import type { TriggerEvent } from '../../../codewhispererChat/storages/triggerEvents' +import { DiffContentProvider } from './diffContentProvider' + +export type ViewDiffMessage = Pick & Partial> export class ContentProvider implements vscode.TextDocumentContentProvider { constructor(private uri: vscode.Uri) {} @@ -44,6 +50,7 @@ export class EditorContentController { ) { const editor = window.activeTextEditor if (editor) { + CWCTelemetryHelper.instance.setDocumentDiagnostics() UserWrittenCodeTracker.instance.onQStartsMakingEdits() const cursorStart = editor.selection.active const indentRange = new vscode.Range(new vscode.Position(cursorStart.line, 0), cursorStart) @@ -155,27 +162,35 @@ export class EditorContentController { * isolating them from any other modifications in the original file. * * @param message the message from Amazon Q chat + * @param scheme the URI scheme to use for the diff view */ - public async viewDiff(message: any, scheme: string = amazonQDiffScheme) { + public async viewDiff(message: ViewDiffMessage, scheme: string = amazonQDiffScheme) { const errorNotification = 'Unable to Open Diff.' - const { filePath, selection } = extractFileAndCodeSelectionFromMessage(message) + const { filePath, fileText, selection } = extractFileAndCodeSelectionFromMessage(message) try { - if (filePath && message?.code?.trim().length > 0 && selection) { - const originalFileUri = vscode.Uri.file(filePath) - const uri = await createTempFileForDiff(originalFileUri, message, selection, scheme) - + if (filePath && message?.code !== undefined && selection) { // Register content provider and show diff - const contentProvider = new ContentProvider(uri) + const contentProvider = new DiffContentProvider() const disposable = vscode.workspace.registerTextDocumentContentProvider(scheme, contentProvider) + + const [originalFileUri, modifiedFileUri] = await createTempUrisForDiff( + filePath, + fileText, + message, + selection, + scheme, + contentProvider + ) + await vscode.commands.executeCommand( 'vscode.diff', originalFileUri, - uri, + modifiedFileUri, `${path.basename(filePath)} ${amazonQTabSuffix}` ) - disposeOnEditorClose(uri, disposable) + disposeOnEditorClose(originalFileUri, disposable) } } catch (error) { void vscode.window.showInformationMessage(errorNotification) diff --git a/packages/core/src/amazonq/commons/controllers/diffContentProvider.ts b/packages/core/src/amazonq/commons/controllers/diffContentProvider.ts new file mode 100644 index 00000000000..5d2e7c2efd0 --- /dev/null +++ b/packages/core/src/amazonq/commons/controllers/diffContentProvider.ts @@ -0,0 +1,51 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import vscode from 'vscode' +import { getLogger } from '../../../shared/logger/logger' + +/** + * A TextDocumentContentProvider that can handle multiple URIs with the same scheme. + * This provider maintains a mapping of URIs to their content. + */ +export class DiffContentProvider implements vscode.TextDocumentContentProvider { + private contentMap = new Map() + private _onDidChange = new vscode.EventEmitter() + + public readonly onDidChange = this._onDidChange.event + + /** + * Register content for a specific URI + * @param uri The URI to register content for + * @param content The content to serve for this URI + */ + public registerContent(uri: vscode.Uri, content: string): void { + this.contentMap.set(uri.toString(), content) + this._onDidChange.fire(uri) + } + + /** + * Unregister a URI + * @param uri The URI to unregister + */ + public unregisterUri(uri: vscode.Uri): void { + this.contentMap.delete(uri.toString()) + } + + /** + * Provides the content for a given URI + * @param uri The URI to provide content for + * @returns The content as a string + */ + public provideTextDocumentContent(uri: vscode.Uri): string { + const content = this.contentMap.get(uri.toString()) + + if (content === undefined) { + getLogger().warn('No content registered for URI: %s', uri.toString()) + return '' + } + + return content + } +} diff --git a/packages/core/src/amazonq/commons/model.ts b/packages/core/src/amazonq/commons/model.ts index 766c5160262..3c5b228d2fc 100644 --- a/packages/core/src/amazonq/commons/model.ts +++ b/packages/core/src/amazonq/commons/model.ts @@ -3,6 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { LicenseUtil } from '../../codewhisperer/util/licenseUtil' +import { CodeReference } from '../../codewhispererChat/view/connector/connector' + // Currently importing ChatItemType in Mynah UI from the vscode side causes an error // TODO remove this once the import stops failing export type ChatItemType = @@ -13,3 +16,10 @@ export type ChatItemType = | 'answer-stream' | 'answer-part' | 'code-result' + +export const referenceLogText = (reference: CodeReference) => + `[${new Date().toLocaleString()}] Accepted recommendation from Amazon Q. Code provided with reference under ${reference.licenseName} license from repository ${reference.repository}.

` diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index 06af40f2c29..14c0e4a59a0 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -20,11 +20,6 @@ export { LspClient } from './lsp/lspClient' export * as lspClient from './lsp/lspClient' export { api } from './extApi' export { AmazonQChatViewProvider } from './webview/webView' -export { init as cwChatAppInit } from '../codewhispererChat/app' -export { init as featureDevChatAppInit } from '../amazonqFeatureDev/app' -export { init as gumbyChatAppInit } from '../amazonqGumby/app' -export { init as testChatAppInit } from '../amazonqTest/app' -export { init as docChatAppInit } from '../amazonqDoc/app' export { amazonQHelpUrl } from '../shared/constants' export * as webviewConstants from './webview/ui/texts/constants' export * as webviewTabConstants from './webview/ui/tabs/constants' @@ -40,7 +35,7 @@ export { computeDiff, } from './commons/diff' export { AuthFollowUpType, AuthMessageDataMap } from './auth/model' -export { ChatItemType } from './commons/model' +export { ChatItemType, referenceLogText } from './commons/model' export { ExtensionMessage } from '../amazonq/webview/ui/commands' export { CodeReference } from '../codewhispererChat/view/connector/connector' export { extractAuthFollowUp } from './util/authUtils' @@ -49,7 +44,10 @@ export * from './lsp/config' export * as WorkspaceLspInstaller from './lsp/workspaceInstaller' export * as secondaryAuth from '../auth/secondaryAuth' export * as authConnection from '../auth/connection' +export * as featureConfig from './webview/generators/featureConfig' +export * as messageDispatcher from './webview/messages/messageDispatcher' import { FeatureContext } from '../shared/featureConfig' +export { EditorContentController, ViewDiffMessage } from './commons/controllers/contentController' /** * main from createMynahUI is a purely browser dependency. Due to this diff --git a/packages/core/src/amazonq/indexNode.ts b/packages/core/src/amazonq/indexNode.ts new file mode 100644 index 00000000000..88a3a4bba37 --- /dev/null +++ b/packages/core/src/amazonq/indexNode.ts @@ -0,0 +1,13 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * These agents have underlying requirements on node dependencies (e.g. jsdom, admzip) + */ +export { init as cwChatAppInit } from '../codewhispererChat/app' +export { init as featureDevChatAppInit } from '../amazonqFeatureDev/app' +export { init as gumbyChatAppInit } from '../amazonqGumby/app' +export { init as testChatAppInit } from '../amazonqTest/app' +export { init as docChatAppInit } from '../amazonqDoc/app' diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index b992fef2fe3..eba89c961c4 100644 --- a/packages/core/src/amazonq/lsp/lspClient.ts +++ b/packages/core/src/amazonq/lsp/lspClient.ts @@ -8,6 +8,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ import * as vscode from 'vscode' +import { oneMB } from '../../shared/utilities/processUtils' import * as path from 'path' import * as nls from 'vscode-nls' import * as crypto from 'crypto' @@ -252,18 +253,20 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths } const serverModule = resourcePaths.lsp + const memoryWarnThreshold = 800 * oneMB const serverOptions = createServerOptions({ encryptionKey: key, - executable: resourcePaths.node, + executable: [resourcePaths.node], serverModule, // TODO(jmkeyes): we always use the debug options...? execArgv: debugOptions.execArgv, + warnThresholds: { memory: memoryWarnThreshold }, }) const documentSelector = [{ scheme: 'file', language: '*' }] - await validateNodeExe(resourcePaths.node, resourcePaths.lsp, debugOptions.execArgv, logger) + await validateNodeExe([resourcePaths.node], resourcePaths.lsp, debugOptions.execArgv, logger) // Options to control the language client const clientOptions: LanguageClientOptions = { diff --git a/packages/core/src/amazonq/webview/generators/featureConfig.ts b/packages/core/src/amazonq/webview/generators/featureConfig.ts new file mode 100644 index 00000000000..8de71be5c5c --- /dev/null +++ b/packages/core/src/amazonq/webview/generators/featureConfig.ts @@ -0,0 +1,35 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeatureConfigProvider, FeatureContext } from '../../../shared/featureConfig' + +export async function getFeatureConfigs(): Promise { + let featureConfigs = new Map() + try { + await FeatureConfigProvider.instance.fetchFeatureConfigs() + featureConfigs = FeatureConfigProvider.getFeatureConfigs() + } catch (error) { + // eslint-disable-next-line aws-toolkits/no-console-log + console.error('Error fetching feature configs:', error) + } + + // Convert featureConfigs to a string suitable for data-features + return JSON.stringify(Array.from(featureConfigs.entries())) +} + +export function serialize(configs: string) { + let featureDataAttributes = '' + try { + // Fetch and parse featureConfigs + const featureConfigs = JSON.parse(configs) + featureDataAttributes = featureConfigs + .map((config: FeatureContext[]) => `data-feature-${config[1].name}="${config[1].variation}"`) + .join(' ') + } catch (error) { + // eslint-disable-next-line aws-toolkits/no-console-log + console.error('Error setting data-feature attribute for featureConfigs:', error) + } + return featureDataAttributes +} diff --git a/packages/core/src/amazonq/webview/generators/webViewContent.ts b/packages/core/src/amazonq/webview/generators/webViewContent.ts index 699cbc2a278..ddbee4f0e9b 100644 --- a/packages/core/src/amazonq/webview/generators/webViewContent.ts +++ b/packages/core/src/amazonq/webview/generators/webViewContent.ts @@ -6,26 +6,12 @@ import path from 'path' import { Uri, Webview } from 'vscode' import { AuthUtil } from '../../../codewhisperer/util/authUtil' -import { FeatureConfigProvider, FeatureContext } from '../../../shared/featureConfig' import globals from '../../../shared/extensionGlobals' import { isSageMaker } from '../../../shared/extensionUtilities' import { AmazonQPromptSettings } from '../../../shared/settings' +import { getFeatureConfigs, serialize } from './featureConfig' export class WebViewContentGenerator { - private async generateFeatureConfigsData(): Promise { - let featureConfigs = new Map() - try { - await FeatureConfigProvider.instance.fetchFeatureConfigs() - featureConfigs = FeatureConfigProvider.getFeatureConfigs() - } catch (error) { - // eslint-disable-next-line aws-toolkits/no-console-log - console.error('Error fetching feature configs:', error) - } - - // Convert featureConfigs to a string suitable for data-features - return JSON.stringify(Array.from(featureConfigs.entries())) - } - public async generate(extensionURI: Uri, webView: Webview): Promise { const entrypoint = process.env.WEBPACK_DEVELOPER_SERVER ? 'http: localhost' @@ -34,30 +20,21 @@ export class WebViewContentGenerator { const contentPolicy = `default-src ${entrypoint} data: blob: 'unsafe-inline'; script-src ${entrypoint} filesystem: ws: wss: 'unsafe-inline';` - let featureDataAttributes = '' - try { - // Fetch and parse featureConfigs - const featureConfigs = JSON.parse(await this.generateFeatureConfigsData()) - featureDataAttributes = featureConfigs - .map((config: FeatureContext[]) => `data-feature-${config[1].name}="${config[1].variation}"`) - .join(' ') - } catch (error) { - // eslint-disable-next-line aws-toolkits/no-console-log - console.error('Error setting data-feature attribute for featureConfigs:', error) - } + const configs = await getFeatureConfigs() + const featureDataAttributes = serialize(configs) return ` Amazon Q (Preview) - ${await this.generateJS(extensionURI, webView)} + ${await this.generateJS(extensionURI, webView, configs)} ` } - private async generateJS(extensionURI: Uri, webView: Webview): Promise { + private async generateJS(extensionURI: Uri, webView: Webview, featureConfigsString: string): Promise { const source = path.join('vue', 'src', 'amazonq', 'webview', 'ui', 'amazonq-ui.js') // Sent to dist/vue folder in webpack. const assetsPath = Uri.joinPath(extensionURI) const javascriptUri = Uri.joinPath(assetsPath, 'dist', source) @@ -77,8 +54,6 @@ export class WebViewContentGenerator { const cssEntrypointsMap = cssEntrypoints.map((item) => webView.asWebviewUri(item)) const cssLinks = cssEntrypointsMap.map((uri) => ``).join('\n') - // Fetch featureConfigs and use it within the script - const featureConfigsString = await this.generateFeatureConfigsData() const isSM = isSageMaker('SMAI') const isSMUS = isSageMaker('SMUS') diff --git a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts index a71120b353c..56d4c8503b9 100644 --- a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts +++ b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts @@ -24,93 +24,110 @@ export function dispatchWebViewMessagesToApps( webViewToAppsMessagePublishers: Map> ) { webview.onDidReceiveMessage((msg) => { - switch (msg.command) { - case 'ui-is-ready': { - DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().setUiReady() - /** - * ui-is-ready isn't associated to any tab so just record the telemetry event and continue. - * This would be equivalent of the duration between "user clicked open q" and "ui has become available" - * NOTE: Amazon Q UI is only loaded ONCE. The state is saved between each hide/show of the webview. - */ + handleWebviewEvent(msg, webViewToAppsMessagePublishers) + }) +} + +export function isLegacyEvent(value: string): boolean { + return ( + value === 'ui-is-ready' || + value === 'start-chat-message-telemetry' || + value === 'update-chat-message-telemetry' || + value === 'stop-chat-message-telemetry' || + value === 'open-link' || + value === 'send-telemetry' || + value === 'disclaimer-acknowledged' || + value === 'update-welcome-count' + ) +} + +export function handleWebviewEvent(msg: any, webViewToAppsMessagePublishers: Map>) { + switch (msg.command) { + case 'ui-is-ready': { + DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().setUiReady() + /** + * ui-is-ready isn't associated to any tab so just record the telemetry event and continue. + * This would be equivalent of the duration between "user clicked open q" and "ui has become available" + * NOTE: Amazon Q UI is only loaded ONCE. The state is saved between each hide/show of the webview. + */ + telemetry.toolkit_didLoadModule.emit({ + module: qChatModuleName, + duration: performance.measure(amazonqMark.uiReady, amazonqMark.open).duration, + result: 'Succeeded', + }) + performance.clearMarks(amazonqMark.uiReady) + performance.clearMarks(amazonqMark.open) + // let cwcController know the ui is ready + webViewToAppsMessagePublishers.get('cwc')?.publish(msg) + return + } + case 'start-chat-message-telemetry': { + AmazonQChatMessageDuration.startChatMessageTelemetry(msg) + return + } + case 'update-chat-message-telemetry': { + AmazonQChatMessageDuration.updateChatMessageTelemetry(msg) + return + } + case 'stop-chat-message-telemetry': { + AmazonQChatMessageDuration.stopChatMessageTelemetry(msg) + return + } + case 'open-link': { + const { link } = msg + void openUrl(Uri.parse(link)) + return + } + case 'send-telemetry': { + if (isOpenAgentTelemetry(msg)) { telemetry.toolkit_didLoadModule.emit({ - module: qChatModuleName, - duration: performance.measure(amazonqMark.uiReady, amazonqMark.open).duration, + module: msg.module, + source: msg.trigger, result: 'Succeeded', }) - performance.clearMarks(amazonqMark.uiReady) - performance.clearMarks(amazonqMark.open) - // let cwcController know the ui is ready - webViewToAppsMessagePublishers.get('cwc')?.publish(msg) - return - } - case 'start-chat-message-telemetry': { - AmazonQChatMessageDuration.startChatMessageTelemetry(msg) - return - } - case 'update-chat-message-telemetry': { - AmazonQChatMessageDuration.updateChatMessageTelemetry(msg) - return - } - case 'stop-chat-message-telemetry': { - AmazonQChatMessageDuration.stopChatMessageTelemetry(msg) return - } - case 'open-link': { - const { link } = msg - void openUrl(Uri.parse(link)) - return - } - case 'send-telemetry': { - if (isOpenAgentTelemetry(msg)) { - telemetry.toolkit_didLoadModule.emit({ - module: msg.module, - source: msg.trigger, - result: 'Succeeded', - }) - return - } else if (isClickTelemetry(msg)) { - telemetry.ui_click.emit({ - elementId: msg.source, - result: 'Succeeded', - }) - return - } - return - } - case 'disclaimer-acknowledged': { - void AmazonQPromptSettings.instance.disablePrompt('amazonQChatDisclaimer') - return - } - case 'update-welcome-count': { - const currentLoadCount = globals.globalState.tryGet('aws.amazonq.welcomeChatShowCount', Number, 0) - void globals.globalState.tryUpdate('aws.amazonq.welcomeChatShowCount', currentLoadCount + 1) + } else if (isClickTelemetry(msg)) { + telemetry.ui_click.emit({ + elementId: msg.source, + result: 'Succeeded', + }) return } + return } - - if (msg.type === 'error') { - if (msg.event === 'toolkit_didLoadModule') { - telemetry.toolkit_didLoadModule.emit({ - module: qChatModuleName, - result: 'Failed', - reasonDesc: msg.errorMessage, - }) - } else { - telemetry.webview_error.emit({ - webviewName: qChatModuleName, - result: 'Failed', - reasonDesc: msg.errorMessage, - }) - } + case 'disclaimer-acknowledged': { + void AmazonQPromptSettings.instance.update('amazonQChatDisclaimer', true) return } - - const appMessagePublisher = webViewToAppsMessagePublishers.get(msg.tabType) - if (appMessagePublisher === undefined) { + case 'update-welcome-count': { + const currentLoadCount = globals.globalState.tryGet('aws.amazonq.welcomeChatShowCount', Number, 0) + void globals.globalState.tryUpdate('aws.amazonq.welcomeChatShowCount', currentLoadCount + 1) return } - appMessagePublisher.publish(msg) - }) + } + + if (msg.type === 'error') { + if (msg.event === 'toolkit_didLoadModule') { + telemetry.toolkit_didLoadModule.emit({ + module: qChatModuleName, + result: 'Failed', + reasonDesc: msg.errorMessage, + }) + } else { + telemetry.webview_error.emit({ + webviewName: qChatModuleName, + result: 'Failed', + reasonDesc: msg.errorMessage, + }) + } + return + } + + const appMessagePublisher = webViewToAppsMessagePublishers.get(msg.tabType) + if (appMessagePublisher === undefined) { + return + } + appMessagePublisher.publish(msg) } export function dispatchAppsMessagesToWebView(webView: Webview, appsMessageListener: MessageListener) { diff --git a/packages/core/src/amazonq/webview/ui/connectorAdapter.ts b/packages/core/src/amazonq/webview/ui/connectorAdapter.ts new file mode 100644 index 00000000000..1de1d8556c4 --- /dev/null +++ b/packages/core/src/amazonq/webview/ui/connectorAdapter.ts @@ -0,0 +1,98 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatPrompt, MynahUI, QuickActionCommandGroup } from '@aws/mynah-ui' +import { isTabType } from './storages/tabsStorage' +import { WebviewUIHandler } from './main' +import { FeatureContext } from '../../../shared/featureConfig' +import { TabDataGenerator } from './tabs/generator' +import { RegionProfile } from '../../../codewhisperer/models/model' +import { ChatClientAdapter, ChatEventHandler } from '@aws/chat-client' + +export class HybridChatAdapter implements ChatClientAdapter { + private uiHandler?: WebviewUIHandler + + private mynahUIRef?: { mynahUI: MynahUI } + + constructor( + private enableAgents: boolean, + private featureConfigsSerialized: [string, FeatureContext][], + private welcomeCount: number, + private disclaimerAcknowledged: boolean, + private regionProfile: RegionProfile | undefined, + private disabledCommands: string[], + private isSMUS: boolean, + private isSM: boolean, + private ideApiPostMessage: (message: any) => void + ) {} + + /** + * First we create the ui handler to get the props, then once mynah UI gets created flare will re-inject the + * mynah UI instance on the hybrid chat adapter + */ + createChatEventHandler(mynahUIRef: { mynahUI: MynahUI }): ChatEventHandler { + this.mynahUIRef = mynahUIRef + + this.uiHandler = new WebviewUIHandler({ + postMessage: this.ideApiPostMessage, + mynahUIRef: this.mynahUIRef, + enableAgents: this.enableAgents, + featureConfigsSerialized: this.featureConfigsSerialized, + welcomeCount: this.welcomeCount, + disclaimerAcknowledged: this.disclaimerAcknowledged, + regionProfile: this.regionProfile, + disabledCommands: this.disabledCommands, + isSMUS: this.isSMUS, + isSM: this.isSM, + hybridChat: true, + }) + + return this.uiHandler.mynahUIProps + } + + isSupportedTab(tabId: string): boolean { + const tabType = this.uiHandler?.tabsStorage.getTab(tabId)?.type + if (!tabType) { + return false + } + return isTabType(tabType) && tabType !== 'cwc' + } + + async handleMessageReceive(message: MessageEvent): Promise { + if (this.uiHandler) { + return this.uiHandler?.connector?.handleMessageReceive(message) + } + + // eslint-disable-next-line aws-toolkits/no-console-log + console.error('unknown message: ', message.data) + } + + isSupportedQuickAction(command: string): boolean { + return ( + command === '/dev' || + command === '/test' || + command === '/review' || + command === '/doc' || + command === '/transform' + ) + } + + handleQuickAction(prompt: ChatPrompt, tabId: string, eventId: string | undefined): void { + return this.uiHandler?.quickActionHandler?.handle(prompt, tabId, eventId) + } + + get initialQuickActions(): QuickActionCommandGroup[] { + const tabDataGenerator = new TabDataGenerator({ + isDocEnabled: this.enableAgents, + isFeatureDevEnabled: this.enableAgents, + isGumbyEnabled: this.enableAgents, + isScanEnabled: this.enableAgents, + isTestEnabled: this.enableAgents, + disabledCommands: this.disabledCommands, + commandHighlight: this.featureConfigsSerialized.find(([name]) => name === 'highlightCommand')?.[1], + }) + return tabDataGenerator.quickActionsGenerator.generateForTab('cwc') ?? [] + } +} diff --git a/packages/core/src/amazonq/webview/ui/main.ts b/packages/core/src/amazonq/webview/ui/main.ts index baf095c19cc..7d5bd48eaeb 100644 --- a/packages/core/src/amazonq/webview/ui/main.ts +++ b/packages/core/src/amazonq/webview/ui/main.ts @@ -66,6 +66,7 @@ export const createMynahUI = ( disabledCommands, isSMUS, isSM, + hybridChat: false, }) return { @@ -120,6 +121,7 @@ export class WebviewUIHandler { disabledCommands, isSMUS, isSM, + hybridChat, }: { postMessage: any mynahUIRef: { mynahUI: MynahUI | undefined } @@ -131,6 +133,7 @@ export class WebviewUIHandler { disabledCommands?: string[] isSMUS?: boolean isSM?: boolean + hybridChat?: boolean }) { this.postMessage = postMessage this.welcomeCount = welcomeCount @@ -207,6 +210,7 @@ export class WebviewUIHandler { isScanEnabled: this.isScanEnabled, isTestEnabled: this.isTestEnabled, isDocEnabled: this.isDocEnabled, + hybridChat, disabledCommands, }) @@ -642,8 +646,27 @@ export class WebviewUIHandler { }) this.mynahUIProps = { - onReady: this.connector.uiReady, + onReady: () => { + // the legacy event flow adds events listeners to the window, we want to avoid these in the lsp flow, since those + // are handled by the flare chat-client + if (hybridChat && this.connector) { + this.connector.isUIReady = true + postMessage({ + command: 'ui-is-ready', + }) + return + } + this.connector?.uiReady() + }, onTabAdd: (tabID: string) => { + if (hybridChat) { + /** + * hybrid chat doesn't support the welcome page + * tabs are already added by the quick action handler + * so theres nothing we have to do here + */ + return + } /** * If the next tab opening will cross the welcome count threshold then * update the next tabs defaults @@ -989,7 +1012,13 @@ export class WebviewUIHandler { }, } - this.mynahUIRef = { mynahUI: new MynahUI(this.mynahUIProps) } + if (!hybridChat) { + /** + * when in hybrid chat the reference gets resolved later so we + * don't need to create mynah UI + */ + this.mynahUIRef = { mynahUI: new MynahUI({ ...this.mynahUIProps, loadStyles: false }) } + } /** * Update the welcome count if we've initially shown @@ -1013,6 +1042,7 @@ export class WebviewUIHandler { isScanEnabled: this.isScanEnabled, isTestEnabled: this.isTestEnabled, isDocEnabled: this.isDocEnabled, + hybridChat, }) this.textMessageHandler = new TextMessageHandler({ mynahUIRef: this.mynahUIRef, diff --git a/packages/core/src/amazonq/webview/ui/quickActions/generator.ts b/packages/core/src/amazonq/webview/ui/quickActions/generator.ts index 81513a3e143..f7bdc6a5089 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/generator.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/generator.ts @@ -60,7 +60,7 @@ export class QuickActionGenerator { command: '/test', icon: MynahIcons.CHECK_LIST, placeholder: 'Specify a function(s) in the current file (optional)', - description: 'Generate unit tests (python & java) for selected code', + description: 'Generate unit tests for selected code', }, ] : []), diff --git a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts index 85c96a1a39c..f0d707247e9 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts @@ -19,6 +19,7 @@ export interface QuickActionsHandlerProps { isScanEnabled: boolean isTestEnabled: boolean isDocEnabled: boolean + hybridChat?: boolean disabledCommands?: string[] } @@ -40,6 +41,7 @@ export class QuickActionHandler { private isScanEnabled: boolean private isTestEnabled: boolean private isDocEnabled: boolean + private isHybridChatEnabled: boolean constructor(props: QuickActionsHandlerProps) { this.mynahUIRef = props.mynahUIRef @@ -58,6 +60,7 @@ export class QuickActionHandler { this.isGumbyEnabled = props.isGumbyEnabled this.isScanEnabled = props.isScanEnabled this.isTestEnabled = props.isTestEnabled + this.isHybridChatEnabled = props.hybridChat ?? false } /** @@ -104,7 +107,7 @@ export class QuickActionHandler { } } - private handleScanCommand(tabID: string, eventId: string | undefined) { + private handleScanCommand(tabID: string | undefined, eventId: string | undefined) { if (!this.isScanEnabled || !this.mynahUI) { return } @@ -123,15 +126,17 @@ export class QuickActionHandler { return } - let affectedTabId: string | undefined = tabID - // if there is no scan tab, open a new one - const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type - if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { - affectedTabId = this.mynahUI.updateStore('', { - loadingChat: true, - }) + /** + * status bar -> "full project scan is now /review" doesn't have a tab ID + * since it's called via a command so we need to manually create one + */ + if (!tabID) { + tabID = this.mynahUI.updateStore('', {}) } + // if there is no scan tab, open a new one + const affectedTabId: string | undefined = this.addTab(tabID) + if (affectedTabId === undefined) { this.mynahUI.notify({ content: uiComponentsTexts.noMoreTabsTooltip, @@ -158,7 +163,7 @@ export class QuickActionHandler { } } - private handleTestCommand(chatPrompt: ChatPrompt, tabID: string, eventId: string | undefined) { + private handleTestCommand(chatPrompt: ChatPrompt, tabID: string | undefined, eventId: string | undefined) { if (!this.isTestEnabled || !this.mynahUI) { return } @@ -172,15 +177,18 @@ export class QuickActionHandler { return } - let affectedTabId: string | undefined = tabID - // if there is no test tab, open a new one - const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type - if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { - affectedTabId = this.mynahUI.updateStore('', { - loadingChat: true, - }) + /** + * right click -> generate test has no tab id + * we have to manually create one if a testgen tab + * wasn't previously created + */ + if (!tabID) { + tabID = this.mynahUI.updateStore('', {}) } + // if there is no test tab, open a new one + const affectedTabId: string | undefined = this.addTab(tabID) + if (affectedTabId === undefined) { this.mynahUI.notify({ content: uiComponentsTexts.noMoreTabsTooltip, @@ -212,12 +220,10 @@ export class QuickActionHandler { return } - let affectedTabId: string | undefined = props.tabID const realPromptText = props.chatPrompt?.escapedPrompt?.trim() ?? '' - const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type - if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { - affectedTabId = this.mynahUI.updateStore('', {}) - } + + const affectedTabId = this.addTab(props.tabID) + if (affectedTabId === undefined) { this.mynahUI.notify({ content: uiComponentsTexts.noMoreTabsTooltip, @@ -305,15 +311,8 @@ export class QuickActionHandler { return } - let affectedTabId: string | undefined = tabID // if there is no gumby tab, open a new one - const currentTabType = this.tabsStorage.getTab(affectedTabId)?.type - if (currentTabType !== 'unknown' && currentTabType !== 'welcome') { - affectedTabId = this.mynahUI.updateStore('', { - loadingChat: true, - cancelButtonWhenLoading: false, - }) - } + const affectedTabId: string | undefined = this.addTab(tabID) if (affectedTabId === undefined) { this.mynahUI.notify({ @@ -343,6 +342,35 @@ export class QuickActionHandler { } } + private addTab(affectedTabId: string | undefined) { + if (!affectedTabId || !this.mynahUI) { + return + } + + const currTab = this.mynahUI.getAllTabs()[affectedTabId] + const currTabWasUsed = + (currTab.store?.chatItems?.filter((item) => item.type === ChatItemType.PROMPT).length ?? 0) > 0 + if (currTabWasUsed) { + affectedTabId = this.mynahUI.updateStore('', { + loadingChat: true, + cancelButtonWhenLoading: false, + }) + } else { + this.mynahUI.updateStore(affectedTabId, { promptInputOptions: [] }) + } + + if (affectedTabId && this.isHybridChatEnabled) { + this.tabsStorage.addTab({ + id: affectedTabId, + type: 'unknown', + status: 'free', + isSelected: true, + }) + } + + return affectedTabId + } + private handleClearCommand(tabID: string) { this.mynahUI?.updateStore(tabID, { chatItems: [], diff --git a/packages/core/src/amazonqDoc/session/session.ts b/packages/core/src/amazonqDoc/session/session.ts index 30d0ed4d874..8cfb5283626 100644 --- a/packages/core/src/amazonqDoc/session/session.ts +++ b/packages/core/src/amazonqDoc/session/session.ts @@ -15,7 +15,7 @@ import { TelemetryHelper } from '../../amazonq/util/telemetryHelper' import { ConversationNotStartedState } from '../../amazonqFeatureDev/session/sessionState' import { logWithConversationId } from '../../amazonqFeatureDev/userFacingText' import { ConversationIdNotFoundError, IllegalStateError } from '../../amazonqFeatureDev/errors' -import { referenceLogText } from '../../amazonqFeatureDev/constants' +import { referenceLogText } from '../../amazonq/commons/model' import { DocInteractionType, DocV2AcceptanceEvent, diff --git a/packages/core/src/amazonqFeatureDev/constants.ts b/packages/core/src/amazonqFeatureDev/constants.ts index 4bc3ca1fdb2..78cae972cc3 100644 --- a/packages/core/src/amazonqFeatureDev/constants.ts +++ b/packages/core/src/amazonqFeatureDev/constants.ts @@ -26,14 +26,6 @@ export const clientErrorMessages = [ 'The folder you chose did not contain any source files in a supported language. Choose another folder and try again.', ] -// License text that's used in codewhisperer reference log -export const referenceLogText = (reference: CodeReference) => - `[${new Date().toLocaleString()}] Accepted recommendation from Amazon Q. Code provided with reference under ${reference.licenseName} license from repository ${reference.repository}.

` - // License text that's used in the file view export const licenseText = (reference: CodeReference) => `${ diff --git a/packages/core/src/amazonqFeatureDev/session/session.ts b/packages/core/src/amazonqFeatureDev/session/session.ts index c6d4452f11f..f89db0aa9e7 100644 --- a/packages/core/src/amazonqFeatureDev/session/session.ts +++ b/packages/core/src/amazonqFeatureDev/session/session.ts @@ -15,7 +15,7 @@ import { UpdateFilesPathsParams, } from '../../amazonq/commons/types' import { ContentLengthError, ConversationIdNotFoundError, IllegalStateError } from '../errors' -import { featureDevChat, referenceLogText, featureDevScheme } from '../constants' +import { featureDevChat, featureDevScheme } from '../constants' import fs from '../../shared/fs/fs' import { FeatureDevClient } from '../client/featureDev' import { codeGenRetryLimit } from '../limits' @@ -34,6 +34,8 @@ import { FollowUpTypes } from '../../amazonq/commons/types' import { SessionConfig } from '../../amazonq/commons/session/sessionConfigFactory' import { Messenger } from '../../amazonq/commons/connector/baseMessenger' import { ContentLengthError as CommonContentLengthError } from '../../shared/errors' +import { referenceLogText } from '../../amazonq/commons/model' + export class Session { private _state?: SessionState | Omit private task: string = '' diff --git a/packages/core/src/amazonqFeatureDev/session/sessionState.ts b/packages/core/src/amazonqFeatureDev/session/sessionState.ts index 5890539409f..5879c16493f 100644 --- a/packages/core/src/amazonqFeatureDev/session/sessionState.ts +++ b/packages/core/src/amazonqFeatureDev/session/sessionState.ts @@ -205,6 +205,14 @@ export class FeatureDevCodeGenState extends BaseCodeGenState { 429 ) } + case codegenResult.codeGenerationStatusDetail?.includes('FileCreationFailed'): { + return new ApiServiceError( + i18n('AWS.amazonq.featureDev.error.codeGen.default'), + 'GetTaskAssistCodeGeneration', + 'FileCreationFailedException', + 500 + ) + } default: { return new ApiServiceError( i18n('AWS.amazonq.featureDev.error.codeGen.default'), diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 43e3ce51dd2..188418eb8bb 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -25,7 +25,6 @@ import { processSQLConversionTransformFormInput, startTransformByQ, stopTransformByQ, - validateCanCompileProject, getValidSQLConversionCandidateProjects, openHilPomFile, } from '../../../codewhisperer/commands/startTransformByQ' @@ -33,7 +32,6 @@ import { JDKVersion, TransformationCandidateProject, transformByQState } from '. import { AbsolutePathDetectedError, AlternateDependencyVersionsNotFoundError, - JavaHomeNotSetError, JobStartError, ModuleUploadError, NoJavaProjectsFoundError, @@ -59,8 +57,10 @@ import { openBuildLogFile, parseBuildFile, validateSQLMetadataFile, + validateCustomVersionsFile, } from '../../../codewhisperer/service/transformByQ/transformFileHandler' import { getAuthType } from '../../../auth/utils' +import fs from '../../../shared/fs/fs' // These events can be interactions within the chat, // or elsewhere in the IDE @@ -243,7 +243,7 @@ export class GumbyController { CodeTransformTelemetryState.instance.setSessionId() this.sessionStorage.getSession().conversationState = ConversationState.WAITING_FOR_TRANSFORMATION_OBJECTIVE - this.messenger.sendStaticTextResponse('choose-transformation-objective', message.tabID) + this.messenger.sendMessage(CodeWhispererConstants.chooseTransformationObjective, message.tabID, 'ai-prompt') this.messenger.sendChatInputEnabled(message.tabID, true) this.messenger.sendUpdatePlaceholder( message.tabID, @@ -299,7 +299,7 @@ export class GumbyController { const validProjects = await this.validateSQLConversionProjects(message) if (validProjects.length > 0) { this.sessionStorage.getSession().updateCandidateProjects(validProjects) - await this.messenger.sendSelectSQLMetadataFileMessage(message.tabID) + this.messenger.sendSelectSQLMetadataFileMessage(message.tabID) } }) .catch((err) => { @@ -383,6 +383,18 @@ export class GumbyController { case ButtonActions.SELECT_SQL_CONVERSION_METADATA_FILE: await this.processMetadataFile(message) break + case ButtonActions.SELECT_CUSTOM_DEPENDENCY_VERSION_FILE: + await this.processCustomDependencyVersionFile(message) + break + case ButtonActions.CONTINUE_TRANSFORMATION_FORM: + this.messenger.sendMessage( + CodeWhispererConstants.continueWithoutYamlMessage, + message.tabID, + 'ai-prompt' + ) + transformByQState.setCustomDependencyVersionFilePath('') + this.promptJavaHome('source', message.tabID) + break case ButtonActions.VIEW_TRANSFORMATION_HUB: await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat) break @@ -403,7 +415,7 @@ export class GumbyController { await this.continueJobWithSelectedDependency(message) break case ButtonActions.CANCEL_DEPENDENCY_FORM: - this.messenger.sendUserPrompt('Cancel', message.tabID) + this.messenger.sendMessage('Cancel', message.tabID, 'prompt') await this.continueTransformationWithoutHIL(message) break case ButtonActions.OPEN_FILE: @@ -448,11 +460,27 @@ export class GumbyController { }) this.messenger.sendOneOrMultipleDiffsMessage(oneOrMultipleDiffsSelection, message.tabID) - // perform local build - await this.validateBuildWithPromptOnError(message) + this.promptJavaHome('source', message.tabID) + // TO-DO: delete line above and uncomment line below when releasing CSB + // await this.messenger.sendCustomDependencyVersionMessage(message.tabID) }) } + private promptJavaHome(type: 'source' | 'target', tabID: any) { + let jdkVersion = undefined + if (type === 'source') { + this.sessionStorage.getSession().conversationState = ConversationState.PROMPT_SOURCE_JAVA_HOME + jdkVersion = transformByQState.getSourceJDKVersion() + } else if (type === 'target') { + this.sessionStorage.getSession().conversationState = ConversationState.PROMPT_TARGET_JAVA_HOME + jdkVersion = transformByQState.getTargetJDKVersion() + } + const message = MessengerUtils.createJavaHomePrompt(jdkVersion) + this.messenger.sendMessage(message, tabID, 'ai-prompt') + this.messenger.sendChatInputEnabled(tabID, true) + this.messenger.sendUpdatePlaceholder(tabID, CodeWhispererConstants.enterJavaHomePlaceholder) + } + private async handleUserLanguageUpgradeProjectChoice(message: any) { await telemetry.codeTransform_submitSelection.run(async () => { const pathToProject: string = message.formSelectedValues['GumbyTransformLanguageUpgradeProjectForm'] @@ -521,32 +549,25 @@ export class GumbyController { }) } - private async prepareLanguageUpgradeProject(message: { pathToJavaHome: string; tabID: string }) { - if (message.pathToJavaHome) { - transformByQState.setJavaHome(message.pathToJavaHome) - getLogger().info( - `CodeTransformation: using JAVA_HOME = ${transformByQState.getJavaHome()} since source JDK does not match Maven JDK` - ) - } - - // Pre-build project locally + private async prepareLanguageUpgradeProject(tabID: string) { + // build project locally try { this.sessionStorage.getSession().conversationState = ConversationState.COMPILING - this.messenger.sendCompilationInProgress(message.tabID) + this.messenger.sendCompilationInProgress(tabID) await compileProject() } catch (err: any) { - this.messenger.sendUnrecoverableErrorResponse('could-not-compile-project', message.tabID) + this.messenger.sendUnrecoverableErrorResponse('could-not-compile-project', tabID) // reset state to allow "Start a new transformation" button to work this.sessionStorage.getSession().conversationState = ConversationState.IDLE throw err } - this.messenger.sendCompilationFinished(message.tabID) + this.messenger.sendCompilationFinished(tabID) // since compilation can potentially take a long time, double check auth const authState = AuthUtil.instance.getAuthState() if (authState !== 'connected') { - void this.messenger.sendAuthNeededExceptionMessage(authState, message.tabID) + void this.messenger.sendAuthNeededExceptionMessage(authState, tabID) this.sessionStorage.getSession().isAuthenticating = true return } @@ -554,33 +575,33 @@ export class GumbyController { // give user a non-blocking warning if build file appears to contain absolute paths await parseBuildFile() - this.messenger.sendAsyncEventProgress( - message.tabID, - true, - undefined, - GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE - ) - this.messenger.sendJobSubmittedMessage(message.tabID) + this.messenger.sendAsyncEventProgress(tabID, true, undefined, GumbyNamedMessages.JOB_SUBMISSION_STATUS_MESSAGE) + this.messenger.sendJobSubmittedMessage(tabID) this.sessionStorage.getSession().conversationState = ConversationState.JOB_SUBMITTED await startTransformByQ() } - // only for Language Upgrades - private async validateBuildWithPromptOnError(message: any | undefined = undefined): Promise { - try { - // Check Java Home is set (not yet prebuilding) - await validateCanCompileProject() - } catch (err: any) { - if (err instanceof JavaHomeNotSetError) { - this.sessionStorage.getSession().conversationState = ConversationState.PROMPT_JAVA_HOME - this.messenger.sendStaticTextResponse('java-home-not-set', message.tabID) - this.messenger.sendChatInputEnabled(message.tabID, true) - this.messenger.sendUpdatePlaceholder(message.tabID, 'Enter the path to your Java installation.') - } + private async processCustomDependencyVersionFile(message: any) { + const fileUri = await vscode.window.showOpenDialog({ + canSelectMany: false, + openLabel: 'Select', + filters: { + 'YAML file': ['yaml'], // restrict user to only pick a .yaml file + }, + }) + if (!fileUri || fileUri.length === 0) { return } + const fileContents = await fs.readFileText(fileUri[0].fsPath) + const isValidFile = await validateCustomVersionsFile(fileContents) - await this.prepareLanguageUpgradeProject(message) + if (!isValidFile) { + this.messenger.sendUnrecoverableErrorResponse('invalid-custom-versions-file', message.tabID) + return + } + this.messenger.sendMessage('Received custom dependency version YAML file.', message.tabID, 'ai-prompt') + transformByQState.setCustomDependencyVersionFilePath(fileUri[0].fsPath) + this.promptJavaHome('source', message.tabID) } private async processMetadataFile(message: any) { @@ -657,19 +678,34 @@ export class GumbyController { } private async processHumanChatMessage(data: { message: string; tabID: string }) { - this.messenger.sendUserPrompt(data.message, data.tabID) + this.messenger.sendMessage(data.message, data.tabID, 'prompt') this.messenger.sendChatInputEnabled(data.tabID, false) - this.messenger.sendUpdatePlaceholder(data.tabID, 'Open a new tab to chat with Q') + this.messenger.sendUpdatePlaceholder(data.tabID, CodeWhispererConstants.openNewTabPlaceholder) const session = this.sessionStorage.getSession() switch (session.conversationState) { - case ConversationState.PROMPT_JAVA_HOME: { + case ConversationState.PROMPT_SOURCE_JAVA_HOME: { const pathToJavaHome = extractPath(data.message) if (pathToJavaHome) { - await this.prepareLanguageUpgradeProject({ - pathToJavaHome, - tabID: data.tabID, - }) + transformByQState.setSourceJavaHome(pathToJavaHome) + // if source and target JDK versions are the same, just re-use the source JAVA_HOME and start the build + if (transformByQState.getTargetJDKVersion() === transformByQState.getSourceJDKVersion()) { + transformByQState.setTargetJavaHome(pathToJavaHome) + await this.prepareLanguageUpgradeProject(data.tabID) + } else { + this.promptJavaHome('target', data.tabID) + } + } else { + this.messenger.sendUnrecoverableErrorResponse('invalid-java-home', data.tabID) + } + break + } + + case ConversationState.PROMPT_TARGET_JAVA_HOME: { + const pathToJavaHome = extractPath(data.message) + if (pathToJavaHome) { + transformByQState.setTargetJavaHome(pathToJavaHome) + await this.prepareLanguageUpgradeProject(data.tabID) // build right after we get target JDK path } else { this.messenger.sendUnrecoverableErrorResponse('invalid-java-home', data.tabID) } @@ -747,7 +783,7 @@ export class GumbyController { }) } - this.messenger.sendStaticTextResponse('end-HIL-early', message.tabID) + this.messenger.sendMessage(CodeWhispererConstants.continueWithoutHilMessage, message.tabID, 'ai-prompt') } } diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts index 2281afff705..b12be675406 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts @@ -8,6 +8,7 @@ * As much as possible, all strings used in the experience should originate here. */ +import vscode from 'vscode' import { AuthFollowUpType, AuthMessageDataMap } from '../../../../amazonq/auth/model' import { JDKVersion, TransformationCandidateProject, transformByQState } from '../../../../codewhisperer/models/model' import { AuthState } from '../../../../auth/auth2' @@ -49,6 +50,7 @@ export type UnrecoverableErrorType = | 'job-start-failed' | 'unsupported-source-db' | 'unsupported-target-db' + | 'invalid-custom-versions-file' | 'error-parsing-sct-file' | 'invalid-zip-no-sct-file' | 'invalid-from-to-jdk' @@ -412,38 +414,12 @@ export class Messenger { this.dispatcher.sendChatMessage(jobSubmittedMessage) } - public sendUserPrompt(prompt: string, tabID: string) { + public sendMessage(prompt: string, tabID: string, type: 'prompt' | 'ai-prompt') { this.dispatcher.sendChatMessage( new ChatMessage( { message: prompt, - messageType: 'prompt', - }, - tabID - ) - ) - } - - public sendStaticTextResponse(messageType: StaticTextResponseType, tabID: string) { - let message = '...' - - switch (messageType) { - case 'java-home-not-set': - message = MessengerUtils.createJavaHomePrompt() - break - case 'end-HIL-early': - message = 'I will continue transforming your code without upgrading this dependency.' - break - case 'choose-transformation-objective': - message = CodeWhispererConstants.chooseTransformationObjective - break - } - - this.dispatcher.sendChatMessage( - new ChatMessage( - { - message, - messageType: 'ai-prompt', + messageType: type, }, tabID ) @@ -482,6 +458,9 @@ export class Messenger { case 'unsupported-target-db': message = CodeWhispererConstants.invalidMetadataFileUnsupportedTargetDB break + case 'invalid-custom-versions-file': + message = CodeWhispererConstants.invalidCustomVersionsFileMessage + break case 'error-parsing-sct-file': message = CodeWhispererConstants.invalidMetadataFileErrorParsing break @@ -664,7 +643,7 @@ ${codeSnippet} this.sendInProgressMessage(tabID, message) } - public sendInProgressMessage(tabID: string, message: string, messageName?: string) { + public sendInProgressMessage(tabID: string, message: string) { this.dispatcher.sendAsyncEventProgress( new AsyncEventProgressMessage(tabID, { inProgress: true, message: undefined }) ) @@ -768,7 +747,56 @@ ${codeSnippet} ) } - public async sendSelectSQLMetadataFileMessage(tabID: string) { + public async sendCustomDependencyVersionMessage(tabID: string) { + const message = CodeWhispererConstants.chooseYamlMessage + const buttons: ChatItemButton[] = [] + + buttons.push({ + keepCardAfterClick: true, + text: 'Select .yaml file', + id: ButtonActions.SELECT_CUSTOM_DEPENDENCY_VERSION_FILE, + disabled: false, + }) + + buttons.push({ + keepCardAfterClick: false, + text: 'Continue without this', + id: ButtonActions.CONTINUE_TRANSFORMATION_FORM, + disabled: false, + }) + + this.dispatcher.sendChatMessage( + new ChatMessage( + { + message, + messageType: 'ai-prompt', + buttons, + }, + tabID + ) + ) + const sampleYAML = `name: "custom-dependency-management" +description: "Custom dependency version management for Java migration from JDK 8/11/17 to JDK 17/21" + +dependencyManagement: + dependencies: + - identifier: "com.example:library1" + targetVersion: "2.1.0" + versionProperty: "library1.version" # Optional + originType: "FIRST_PARTY" # or "THIRD_PARTY" # Optional + - identifier: "com.example:library2" + targetVersion: "3.0.0" + originType: "THIRD_PARTY" + plugins: + - identifier: "com.example.plugin" + targetVersion: "1.2.0" + versionProperty: "plugin.version" # Optional` + + const doc = await vscode.workspace.openTextDocument({ content: sampleYAML, language: 'yaml' }) + await vscode.window.showTextDocument(doc) + } + + public sendSelectSQLMetadataFileMessage(tabID: string) { const message = CodeWhispererConstants.selectSQLMetadataFileHelpMessage const buttons: ChatItemButton[] = [] diff --git a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts index a7275ac98a3..ad1aade7c7e 100644 --- a/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts +++ b/packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts @@ -5,7 +5,7 @@ */ import * as os from 'os' -import { transformByQState, JDKVersion } from '../../../../codewhisperer/models/model' +import { JDKVersion } from '../../../../codewhisperer/models/model' import * as CodeWhispererConstants from '../../../../codewhisperer/models/constants' import DependencyVersions from '../../../models/dependencies' @@ -20,6 +20,8 @@ export enum ButtonActions { CONFIRM_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormConfirm', CONFIRM_SELECTIVE_TRANSFORMATION_FORM = 'gumbyTransformOneOrMultipleDiffsFormConfirm', SELECT_SQL_CONVERSION_METADATA_FILE = 'gumbySQLConversionMetadataTransformFormConfirm', + SELECT_CUSTOM_DEPENDENCY_VERSION_FILE = 'gumbyCustomDependencyVersionTransformFormConfirm', + CONTINUE_TRANSFORMATION_FORM = 'gumbyTransformFormContinue', CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm', CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel', CONFIRM_JAVA_HOME_FORM = 'gumbyJavaHomeFormConfirm', @@ -35,14 +37,11 @@ export enum GumbyCommands { } export default class MessengerUtils { - static createJavaHomePrompt = (): string => { - let javaHomePrompt = `${ - CodeWhispererConstants.enterJavaHomeChatMessage - } ${transformByQState.getSourceJDKVersion()}. \n` + static createJavaHomePrompt = (jdkVersion: JDKVersion | undefined): string => { + let javaHomePrompt = `${CodeWhispererConstants.enterJavaHomeChatMessage} ${jdkVersion}. \n` if (os.platform() === 'win32') { javaHomePrompt += CodeWhispererConstants.windowsJavaHomeHelpChatMessage } else if (os.platform() === 'darwin') { - const jdkVersion = transformByQState.getSourceJDKVersion() if (jdkVersion === JDKVersion.JDK8) { javaHomePrompt += ` ${CodeWhispererConstants.macJavaVersionHomeHelpChatMessage(1.8)}` } else if (jdkVersion === JDKVersion.JDK11) { diff --git a/packages/core/src/amazonqGumby/chat/session/session.ts b/packages/core/src/amazonqGumby/chat/session/session.ts index b0ed125c7d8..f1a69eb60ff 100644 --- a/packages/core/src/amazonqGumby/chat/session/session.ts +++ b/packages/core/src/amazonqGumby/chat/session/session.ts @@ -7,7 +7,8 @@ import { TransformationCandidateProject } from '../../../codewhisperer/models/mo export enum ConversationState { IDLE, - PROMPT_JAVA_HOME, + PROMPT_SOURCE_JAVA_HOME, + PROMPT_TARGET_JAVA_HOME, COMPILING, JOB_SUBMITTED, WAITING_FOR_HIL_INPUT, diff --git a/packages/core/src/auth/index.ts b/packages/core/src/auth/index.ts index 3fd687e184b..b0dfb868fa5 100644 --- a/packages/core/src/auth/index.ts +++ b/packages/core/src/auth/index.ts @@ -25,3 +25,4 @@ export { LoginManager } from './deprecated/loginManager' export * as constants from './sso/constants' export * as authUtils from './utils' export * as auth2 from './auth2' +export * as SsoAccessTokenProvider from './sso/ssoAccessTokenProvider' diff --git a/packages/core/src/auth/sso/clients.ts b/packages/core/src/auth/sso/clients.ts index 01d0e031d04..e050bdc793e 100644 --- a/packages/core/src/auth/sso/clients.ts +++ b/packages/core/src/auth/sso/clients.ts @@ -258,7 +258,7 @@ function addLoggingMiddleware(client: SSOOIDCClient) { args.input as unknown as Record, 3, ['clientSecret', 'accessToken', 'refreshToken'], - '[omitted]' + { replacement: '[omitted]' } ) getLogger().debug('API request (%s %s): %O', hostname, path, input) } @@ -288,7 +288,7 @@ function addLoggingMiddleware(client: SSOOIDCClient) { result.output as unknown as Record, 3, ['clientSecret', 'accessToken', 'refreshToken'], - '[omitted]' + { replacement: '[omitted]' } ) getLogger().debug('API response (%s %s): %O', hostname, path, output) } diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index 6dab3d5cb90..e4aaaeda934 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -72,12 +72,7 @@ import { AuthUtil } from './util/authUtil' import { ImportAdderProvider } from './service/importAdderProvider' import { TelemetryHelper } from './util/telemetryHelper' import { openUrl } from '../shared/utilities/vsCodeUtils' -import { - getAvailableCustomizationsList, - getSelectedCustomization, - notifyNewCustomizations, - switchToBaseCustomizationAndNotify, -} from './util/customizationUtil' +import { notifyNewCustomizations, onProfileChangedListener } from './util/customizationUtil' import { CodeWhispererCommandBackend, CodeWhispererCommandDeclarations } from './commands/gettingStartedPageCommands' import { SecurityIssueHoverProvider } from './service/securityIssueHoverProvider' import { SecurityIssueCodeActionProvider } from './service/securityIssueCodeActionProvider' @@ -95,6 +90,8 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr import { setContext } from '../shared/vscode/setContext' import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' import { detectCommentAboveLine } from '../shared/utilities/commentUtils' +import { activateEditTracking } from './nextEditPrediction/activation' +import { notifySelectDeveloperProfile } from './region/utils' let localize: nls.LocalizeFunc @@ -104,8 +101,6 @@ export async function activate(context: ExtContext): Promise { // Import old CodeWhisperer settings into Amazon Q await CodeWhispererSettings.instance.importSettings() - const auth = AuthUtil.instance - // TODO: is this indirection useful? registerDeclaredCommands( context.extensionContext.subscriptions, @@ -156,7 +151,7 @@ export async function activate(context: ExtContext): Promise { if (configurationChangeEvent.affectsConfiguration('amazonQ.showCodeWithReferences')) { ReferenceLogViewProvider.instance.update() - if (auth.isIdcConnection()) { + if (AuthUtil.instance.isIdcConnection()) { await vscode.window .showInformationMessage( CodeWhispererConstants.ssoConfigAlertMessage, @@ -171,7 +166,7 @@ export async function activate(context: ExtContext): Promise { } if (configurationChangeEvent.affectsConfiguration('amazonQ.shareContentWithAWS')) { - if (auth.isIdcConnection()) { + if (AuthUtil.instance.isIdcConnection()) { await vscode.window .showInformationMessage( CodeWhispererConstants.ssoConfigAlertMessageShareData, @@ -341,48 +336,33 @@ export async function activate(context: ExtContext): Promise { SecurityIssueCodeActionProvider.instance ), vscode.commands.registerCommand('aws.amazonq.openEditorAtRange', openEditorAtRange), - auth.regionProfileManager.onDidChangeRegionProfile(() => { - // Validate user still has access to the selected customization. - const selectedCustomization = getSelectedCustomization() - // No need to validate base customization which has empty arn. - if (selectedCustomization.arn.length > 0) { - getAvailableCustomizationsList() - .then(async (customizations) => { - const r = customizations.find((it) => it.arn === selectedCustomization.arn) - if (!r) { - await switchToBaseCustomizationAndNotify() - } - }) - .catch((e) => { - getLogger().error( - `encounter error while validating selected customization on profile change: %s`, - (e as Error).message - ) - }) - } - }) + AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(onProfileChangedListener) ) // run the auth startup code with context for telemetry await telemetry.function_call.run( async () => { - if (auth.isConnectionExpired()) { - auth.showReauthenticatePrompt().catch((e) => { + if (AuthUtil.instance.isConnectionExpired()) { + AuthUtil.instance.showReauthenticatePrompt().catch((e) => { const defaulMsg = localize('AWS.generic.message.error', 'Failed to reauth:') void logAndShowError(localize, e, 'showReauthenticatePrompt', defaulMsg) }) - if (auth.isIdcConnection()) { - await auth.notifySessionConfiguration() + if (AuthUtil.instance.isIdcConnection()) { + await AuthUtil.instance.notifySessionConfiguration() } } + + if (AuthUtil.instance.regionProfileManager.requireProfileSelection()) { + await notifySelectDeveloperProfile() + } }, { emit: false, functionId: { name: 'activateCwCore' } } ) - if (auth.isIdcConnection() && auth.isConnected()) { + if (AuthUtil.instance.isIdcConnection() && AuthUtil.instance.isConnected()) { await notifyNewCustomizations() } - if (auth.isBuilderIdConnection()) { + if (AuthUtil.instance.isBuilderIdConnection()) { await CodeScansState.instance.setScansEnabled(false) } @@ -397,8 +377,8 @@ export async function activate(context: ExtContext): Promise { return ( (isScansEnabled ?? CodeScansState.instance.isScansEnabled()) && !CodeScansState.instance.isMonthlyQuotaExceeded() && - auth.isConnected() && - !auth.isBuilderIdConnection() && + AuthUtil.instance.isConnected() && + !AuthUtil.instance.isBuilderIdConnection() && editor && editor.document.uri.scheme === 'file' && securityScanLanguageContext.isLanguageSupported(editor.document.languageId) @@ -519,6 +499,8 @@ export async function activate(context: ExtContext): Promise { }) ) } + + activateEditTracking(context) } export async function shutdown() { diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 305f36f9659..0a473dfdccd 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -7,18 +7,16 @@ import { AWSError, Credentials, Service } from 'aws-sdk' import globals from '../../shared/extensionGlobals' import * as CodeWhispererClient from './codewhispererclient' import * as CodeWhispererUserClient from './codewhispereruserclient' -import { ListAvailableCustomizationsResponse, SendTelemetryEventRequest } from './codewhispereruserclient' +import { SendTelemetryEventRequest } from './codewhispereruserclient' import { ServiceOptions } from '../../shared/awsClientBuilder' import { hasVendedIamCredentials } from '../../auth/auth' import { CodeWhispererSettings } from '../util/codewhispererSettings' import { PromiseResult } from 'aws-sdk/lib/request' import { AuthUtil } from '../util/authUtil' -import { pageableToCollection } from '../../shared/utilities/collectionUtils' import apiConfig = require('./service-2.json') import userApiConfig = require('./user-service-2.json') import { session } from '../util/codeWhispererSession' import { getLogger } from '../../shared/logger/logger' -import { indent } from '../../shared/utilities/textUtilities' import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util' import { extensionVersion, getServiceEnvVarConfig } from '../../shared/vscode/env' import { DevSettings } from '../../shared/settings' @@ -220,28 +218,6 @@ export class DefaultCodeWhispererClient { .promise() } - public async listAvailableCustomizations(): Promise { - const client = await this.createUserSdkClient() - const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile - const requester = async (request: CodeWhispererUserClient.ListAvailableCustomizationsRequest) => - client.listAvailableCustomizations(request).promise() - return pageableToCollection(requester, { profileArn: profile?.arn }, 'nextToken') - .promise() - .then((resps) => { - let logStr = 'amazonq: listAvailableCustomizations API request:' - for (const resp of resps) { - const requestId = resp.$response.requestId - logStr += `\n${indent('RequestID: ', 4)}${requestId},\n${indent('Customizations:', 4)}` - for (const [index, c] of resp.customizations.entries()) { - const entry = `${index.toString().padStart(2, '0')}: ${c.name?.trim()}` - logStr += `\n${indent(entry, 8)}` - } - } - getLogger().debug(logStr) - return resps - }) - } - public async sendTelemetryEvent(request: SendTelemetryEventRequest) { const requestWithCommonFields: SendTelemetryEventRequest = { ...request, diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index 85b6c8ee44e..82bcba7e9e4 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -368,6 +368,9 @@ export const openSecurityIssuePanel = Commands.declare( const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath await showSecurityIssueWebview(context.extensionContext, targetIssue, targetFilePath) + if (targetIssue.suggestedFixes.length === 0) { + await generateFix.execute(targetIssue, targetFilePath, 'webview', true, false) + } telemetry.codewhisperer_codeScanIssueViewDetails.emit({ findingId: targetIssue.findingId, detectorId: targetIssue.detectorId, @@ -386,9 +389,6 @@ export const openSecurityIssuePanel = Commands.declare( undefined, !!targetIssue.suggestedFixes.length ) - if (targetIssue.suggestedFixes.length === 0) { - await generateFix.execute(targetIssue, targetFilePath, 'webview', true, false) - } } ) diff --git a/packages/core/src/codewhisperer/commands/startTransformByQ.ts b/packages/core/src/codewhisperer/commands/startTransformByQ.ts index 37b85be3fad..eb31839686d 100644 --- a/packages/core/src/codewhisperer/commands/startTransformByQ.ts +++ b/packages/core/src/codewhisperer/commands/startTransformByQ.ts @@ -56,7 +56,6 @@ import { submitFeedback } from '../../feedback/vue/submitFeedback' import { placeholder } from '../../shared/vscode/commands2' import { AlternateDependencyVersionsNotFoundError, - JavaHomeNotSetError, JobStartError, ModuleUploadError, PollJobError, @@ -69,8 +68,7 @@ import { getJsonValuesFromManifestFile, highlightPomIssueInProject, parseVersionsListFromPomFile, - setMaven, - writeLogs, + writeAndShowBuildLogs, } from '../service/transformByQ/transformFileHandler' import { sleep } from '../../shared/utilities/timeoutUtils' import DependencyVersions from '../../amazonqGumby/models/dependencies' @@ -111,37 +109,6 @@ export async function processSQLConversionTransformFormInput(pathToProject: stri transformByQState.setTargetJDKVersion(JDKVersion.JDK17) } -async function validateJavaHome(): Promise { - const versionData = await getVersionData() - let javaVersionUsedByMaven = versionData[1] - if (javaVersionUsedByMaven !== undefined) { - javaVersionUsedByMaven = javaVersionUsedByMaven.slice(0, 3) - if (javaVersionUsedByMaven === '1.8') { - javaVersionUsedByMaven = JDKVersion.JDK8 - } else if (javaVersionUsedByMaven === '11.') { - javaVersionUsedByMaven = JDKVersion.JDK11 - } else if (javaVersionUsedByMaven === '17.') { - javaVersionUsedByMaven = JDKVersion.JDK17 - } else if (javaVersionUsedByMaven === '21.') { - javaVersionUsedByMaven = JDKVersion.JDK21 - } - } - if (javaVersionUsedByMaven !== transformByQState.getSourceJDKVersion()) { - // means either javaVersionUsedByMaven is undefined or it does not match the project JDK - return false - } - - return true -} - -export async function validateCanCompileProject() { - await setMaven() - const javaHomeFound = await validateJavaHome() - if (!javaHomeFound) { - throw new JavaHomeNotSetError() - } -} - export async function compileProject() { try { const dependenciesFolder: FolderInfo = getDependenciesFolderInfo() @@ -150,9 +117,7 @@ export async function compileProject() { await prepareProjectDependencies(dependenciesFolder, modulePath) } catch (err) { // open build-logs.txt file to show user error logs - const logFilePath = await writeLogs() - const doc = await vscode.workspace.openTextDocument(logFilePath) - await vscode.window.showTextDocument(doc) + await writeAndShowBuildLogs(true) throw err } } @@ -300,7 +265,7 @@ export async function initiateHumanInTheLoopPrompt(jobId: string) { const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile const humanInTheLoopManager = HumanInTheLoopManager.instance // 1) We need to call GetTransformationPlan to get artifactId - const transformationSteps = await getTransformationSteps(jobId, false, profile) + const transformationSteps = await getTransformationSteps(jobId, profile) const { transformationStep, progressUpdate } = findDownloadArtifactStep(transformationSteps) if (!transformationStep || !progressUpdate) { @@ -566,7 +531,7 @@ export async function pollTransformationStatusUntilPlanReady(jobId: string, prof try { const tempToolkitFolder = await makeTemporaryToolkitFolder() const tempBuildLogsDir = path.join(tempToolkitFolder, 'q-transformation-build-logs') - await downloadAndExtractResultArchive(jobId, undefined, tempBuildLogsDir, 'Logs') + await downloadAndExtractResultArchive(jobId, tempBuildLogsDir) pathToLog = path.join(tempBuildLogsDir, 'buildCommandOutput.log') transformByQState.setPreBuildLogFilePath(pathToLog) } catch (e) { @@ -788,7 +753,8 @@ export async function postTransformationJob() { } if (transformByQState.getPayloadFilePath() !== '') { - fs.rmSync(transformByQState.getPayloadFilePath(), { recursive: true, force: true }) // delete ZIP if it exists + // delete original upload ZIP at very end of transformation + fs.rmSync(transformByQState.getPayloadFilePath(), { recursive: true, force: true }) } // attempt download for user diff --git a/packages/core/src/codewhisperer/commands/types.ts b/packages/core/src/codewhisperer/commands/types.ts index e211ae76f9a..cec28829507 100644 --- a/packages/core/src/codewhisperer/commands/types.ts +++ b/packages/core/src/codewhisperer/commands/types.ts @@ -18,6 +18,8 @@ export const firstStartUpSource = ExtStartUpSources.firstStartUp export const cwEllipsesMenu = 'ellipsesMenu' /** Indicates a CodeWhisperer command was executed from the command palette */ export const commandPalette = 'commandPalette' +/** Indicates a CodeWhisperer command was executed as a result of a toast message interaction */ +export const toastMessage = 'toastMessage' /** * Indicates what caused the CodeWhisperer command to be executed, since a command can be executed from different "sources" @@ -35,3 +37,4 @@ export type CodeWhispererSource = | typeof firstStartUpSource | typeof cwEllipsesMenu | typeof commandPalette + | typeof toastMessage diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 930b168beec..e49b43ba3f1 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -89,17 +89,25 @@ export * from './util/securityScanLanguageContext' export * from './util/importAdderUtil' export * from './util/globalStateUtil' export * from './util/zipUtil' +export * from './util/diagnosticsUtil' export * from './util/commonUtil' export * from './util/supplementalContext/codeParsingUtil' export * from './util/supplementalContext/supplementalContextUtil' export * from './util/codewhispererSettings' +export * as getStartUrl from './util/getStartUrl' export * as supplementalContextUtil from './util/supplementalContext/supplementalContextUtil' export * from './service/diagnosticsProvider' export * as diagnosticsProvider from './service/diagnosticsProvider' export * from './ui/codeWhispererNodes' export { SecurityScanError, SecurityScanTimedOutError } from '../codewhisperer/models/errors' export * as CodeWhispererConstants from '../codewhisperer/models/constants' -export { getSelectedCustomization, setSelectedCustomization, baseCustomization } from './util/customizationUtil' +export { + getSelectedCustomization, + setSelectedCustomization, + baseCustomization, + onProfileChangedListener, + CustomizationProvider, +} from './util/customizationUtil' export { Container } from './service/serviceContainer' export * from './util/gitUtil' export * from './ui/prompters' diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 9d17b166cb8..73b0b475a2b 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -87,6 +87,9 @@ export const lineBreakWin = '\r\n' export const supplementalContextTimeoutInMs = 100 export const supplementalContextMaxTotalLength = 20480 + +export const editorStateMaxLength = 40000 + /** * Ux of recommendations */ @@ -599,6 +602,9 @@ export const invalidMetadataFileUnsupportedSourceDB = export const invalidMetadataFileUnsupportedTargetDB = 'I can only convert SQL for migrations to Aurora PostgreSQL or Amazon RDS for PostgreSQL target databases. The provided .sct file indicates another target database for this migration.' +export const invalidCustomVersionsFileMessage = + 'Your .YAML file is not formatted correctly. Make sure that the .YAML file you upload follows the format of the sample file provided.' + export const invalidMetadataFileErrorParsing = "It looks like the .sct file you provided isn't valid. Make sure that you've uploaded the .zip file you retrieved from your schema conversion in AWS DMS." @@ -655,6 +661,17 @@ export const jobCancelledChatMessage = export const jobCancelledNotification = 'You cancelled the transformation.' +export const continueWithoutHilMessage = 'I will continue transforming your code without upgrading this dependency.' + +export const continueWithoutYamlMessage = 'Ok, I will continue without this information.' + +export const chooseYamlMessage = + 'You can optionally upload a YAML file to specify which dependency versions to upgrade to.' + +export const enterJavaHomePlaceholder = 'Enter the path to your Java installation' + +export const openNewTabPlaceholder = 'Open a new tab to chat with Q' + export const diffMessage = (multipleDiffs: boolean) => { return multipleDiffs ? 'You can review the diffs to see my proposed changes and accept or reject them. You will be able to accept changes from one diff at a time. If you reject changes in one diff, you will not be able to view or accept changes in the other diffs.' @@ -756,7 +773,7 @@ export const cleanInstallErrorChatMessage = `Sorry, I couldn\'t run the Maven cl export const cleanInstallErrorNotification = `Amazon Q could not run the Maven clean install command to build your project. For more information, see the [Amazon Q documentation](${codeTransformTroubleshootMvnFailure}).` -export const enterJavaHomeChatMessage = 'Enter the path to JDK ' +export const enterJavaHomeChatMessage = 'Enter the path to JDK' export const projectPromptChatMessage = 'I can upgrade your Java project. To start the transformation, I need some information from you. Choose the project you want to upgrade and the target code version to upgrade to. Then, choose Confirm.' @@ -931,3 +948,10 @@ export const testGenExcludePatterns = [ '**/*.deb', '**/*.model', ] + +export const predictionTrackerDefaultConfig = { + maxStorageSizeKb: 5000, + debounceIntervalMs: 2000, + maxAgeMs: 30000, + maxSupplementalContext: 15, +} diff --git a/packages/core/src/codewhisperer/models/model.ts b/packages/core/src/codewhisperer/models/model.ts index 99689748320..28072249371 100644 --- a/packages/core/src/codewhisperer/models/model.ts +++ b/packages/core/src/codewhisperer/models/model.ts @@ -678,12 +678,14 @@ export enum BuildSystem { Unknown = 'Unknown', } +// TO-DO: include the custom YAML file path here somewhere? export class ZipManifest { sourcesRoot: string = 'sources/' dependenciesRoot: string = 'dependencies/' buildLogs: string = 'build-logs.txt' version: string = '1.0' hilCapabilities: string[] = ['HIL_1pDependency_VersionUpgrade'] + // TO-DO: add 'CLIENT_SIDE_BUILD' here when releasing transformCapabilities: string[] = ['EXPLAINABILITY_V1'] customBuildCommand: string = 'clean test' requestedConversions?: { @@ -771,6 +773,8 @@ export class TransformByQState { private metadataPathSQL: string = '' + private customVersionPath: string = '' + private linesOfCodeSubmitted: number | undefined = undefined private planFilePath: string = '' @@ -790,11 +794,13 @@ export class TransformByQState { private jobFailureErrorChatMessage: string | undefined = undefined - private errorLog: string = '' + private buildLog: string = '' private mavenName: string = '' - private javaHome: string | undefined = undefined + private sourceJavaHome: string | undefined = undefined + + private targetJavaHome: string | undefined = undefined private chatControllers: ChatControllerEventEmitters | undefined = undefined private chatMessenger: Messenger | undefined = undefined @@ -897,6 +903,10 @@ export class TransformByQState { return this.metadataPathSQL } + public getCustomDependencyVersionFilePath() { + return this.customVersionPath + } + public getStatus() { return this.transformByQState } @@ -937,16 +947,20 @@ export class TransformByQState { return this.jobFailureErrorChatMessage } - public getErrorLog() { - return this.errorLog + public getBuildLog() { + return this.buildLog } public getMavenName() { return this.mavenName } - public getJavaHome() { - return this.javaHome + public getSourceJavaHome() { + return this.sourceJavaHome + } + + public getTargetJavaHome() { + return this.targetJavaHome } public getChatControllers() { @@ -969,8 +983,12 @@ export class TransformByQState { return this.intervalId } - public appendToErrorLog(message: string) { - this.errorLog += `${message}\n\n` + public appendToBuildLog(message: string) { + this.buildLog += `${message}\n\n` + } + + public clearBuildLog() { + this.buildLog = '' } public setToNotStarted() { @@ -1061,6 +1079,10 @@ export class TransformByQState { this.metadataPathSQL = path } + public setCustomDependencyVersionFilePath(path: string) { + this.customVersionPath = path + } + public setPlanFilePath(filePath: string) { this.planFilePath = filePath } @@ -1101,8 +1123,12 @@ export class TransformByQState { this.mavenName = mavenName } - public setJavaHome(javaHome: string) { - this.javaHome = javaHome + public setSourceJavaHome(javaHome: string) { + this.sourceJavaHome = javaHome + } + + public setTargetJavaHome(javaHome: string) { + this.targetJavaHome = javaHome } public setChatControllers(controllers: ChatControllerEventEmitters) { @@ -1144,6 +1170,7 @@ export class TransformByQState { this.jobFailureMetadata = '' this.payloadFilePath = '' this.metadataPathSQL = '' + this.customVersionPath = '' this.sourceJDKVersion = undefined this.targetJDKVersion = undefined this.sourceDB = undefined @@ -1151,7 +1178,7 @@ export class TransformByQState { this.sourceServerName = '' this.schemaOptions.clear() this.schema = '' - this.errorLog = '' + this.buildLog = '' this.customBuildCommand = '' this.intervalId = undefined this.produceMultipleDiffs = false diff --git a/packages/core/src/codewhisperer/nextEditPrediction/activation.ts b/packages/core/src/codewhisperer/nextEditPrediction/activation.ts new file mode 100644 index 00000000000..f302cec2ad5 --- /dev/null +++ b/packages/core/src/codewhisperer/nextEditPrediction/activation.ts @@ -0,0 +1,32 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { PredictionTracker } from './predictionTracker' +import { PredictionKeyStrokeHandler } from './predictionKeyStrokeHandler' +import { getLogger } from '../../shared/logger/logger' +import { ExtContext } from '../../shared/extensions' + +export let predictionTracker: PredictionTracker | undefined +let keyStrokeHandler: PredictionKeyStrokeHandler | undefined + +export function activateEditTracking(context: ExtContext): void { + try { + predictionTracker = new PredictionTracker(context.extensionContext) + + keyStrokeHandler = new PredictionKeyStrokeHandler(predictionTracker) + context.extensionContext.subscriptions.push( + vscode.Disposable.from({ + dispose: () => { + keyStrokeHandler?.dispose() + }, + }) + ) + + getLogger('nextEditPrediction').debug('Next Edit Prediction activated') + } catch (error) { + getLogger('nextEditPrediction').error(`Error in activateEditTracking: ${error}`) + } +} diff --git a/packages/core/src/codewhisperer/nextEditPrediction/diffContextGenerator.ts b/packages/core/src/codewhisperer/nextEditPrediction/diffContextGenerator.ts new file mode 100644 index 00000000000..9f379b82a4e --- /dev/null +++ b/packages/core/src/codewhisperer/nextEditPrediction/diffContextGenerator.ts @@ -0,0 +1,154 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as diff from 'diff' +import { getLogger } from '../../shared/logger/logger' +import * as codewhispererClient from '../client/codewhisperer' +import { supplementalContextMaxTotalLength, charactersLimit } from '../models/constants' + +const logger = getLogger('nextEditPrediction') + +/** + * Generates a unified diff format between old and new file contents + */ +function generateUnifiedDiffWithTimestamps( + oldFilePath: string, + newFilePath: string, + oldContent: string, + newContent: string, + oldTimestamp: number, + newTimestamp: number, + contextSize: number = 3 +): string { + const patchResult = diff.createTwoFilesPatch( + oldFilePath, + newFilePath, + oldContent, + newContent, + String(oldTimestamp), + String(newTimestamp), + { context: contextSize } + ) + + // Remove unused headers + const lines = patchResult.split('\n') + if (lines.length >= 2 && lines[0].startsWith('Index:')) { + lines.splice(0, 2) + return lines.join('\n') + } + + return patchResult +} + +export interface SnapshotContent { + filePath: string + content: string + timestamp: number +} + +/** + * Generates supplemental contexts from snapshot contents and current content + * + * @param filePath - Path to the file + * @param currentContent - Current content of the file + * @param snapshotContents - List of snapshot contents sorted by timestamp (oldest first) + * @param maxContexts - Maximum number of supplemental contexts to return + * @returns Array of SupplementalContext objects, T_0 being the snapshot of current file content: + * U0: udiff of T_0 and T_1 + * U1: udiff of T_0 and T_2 + * U2: udiff of T_0 and T_3 + */ +export function generateDiffContexts( + filePath: string, + currentContent: string, + snapshotContents: SnapshotContent[], + maxContexts: number +): codewhispererClient.SupplementalContext[] { + if (snapshotContents.length === 0) { + return [] + } + + const supplementalContexts: codewhispererClient.SupplementalContext[] = [] + const currentTimestamp = Date.now() + + for (let i = snapshotContents.length - 1; i >= 0; i--) { + const snapshot = snapshotContents[i] + try { + const unifiedDiff = generateUnifiedDiffWithTimestamps( + snapshot.filePath, + filePath, + snapshot.content, + currentContent, + snapshot.timestamp, + currentTimestamp + ) + + supplementalContexts.push({ + filePath: snapshot.filePath, + content: unifiedDiff, + type: 'PreviousEditorState', + metadata: { + previousEditorStateMetadata: { + timeOffset: currentTimestamp - snapshot.timestamp, + }, + }, + }) + } catch (err) { + logger.error(`Failed to generate diff: ${err}`) + } + } + + const trimmedContext = trimSupplementalContexts(supplementalContexts, maxContexts) + logger.debug( + `supplemental contexts: ${trimmedContext.length} contexts, total size: ${trimmedContext.reduce((sum, ctx) => sum + ctx.content.length, 0)} characters` + ) + return trimmedContext +} + +/** + * Trims the supplementalContexts array to ensure it doesn't exceed the max number + * of contexts or total character length limit + * + * @param supplementalContexts - Array of SupplementalContext objects (already sorted with newest first) + * @param maxContexts - Maximum number of supplemental contexts allowed + * @returns Trimmed array of SupplementalContext objects + */ +export function trimSupplementalContexts( + supplementalContexts: codewhispererClient.SupplementalContext[], + maxContexts: number +): codewhispererClient.SupplementalContext[] { + if (supplementalContexts.length === 0) { + return supplementalContexts + } + + // First filter out any individual context that exceeds the character limit + let result = supplementalContexts.filter((context) => { + return context.content.length <= charactersLimit + }) + + // Then limit by max number of contexts + if (result.length > maxContexts) { + result = result.slice(0, maxContexts) + } + + // Lastly enforce total character limit + let totalLength = 0 + let i = 0 + + while (i < result.length) { + totalLength += result[i].content.length + if (totalLength > supplementalContextMaxTotalLength) { + break + } + i++ + } + + if (i === result.length) { + return result + } + + const trimmedContexts = result.slice(0, i) + return trimmedContexts +} diff --git a/packages/core/src/codewhisperer/nextEditPrediction/predictionKeyStrokeHandler.ts b/packages/core/src/codewhisperer/nextEditPrediction/predictionKeyStrokeHandler.ts new file mode 100644 index 00000000000..b09272f0d8b --- /dev/null +++ b/packages/core/src/codewhisperer/nextEditPrediction/predictionKeyStrokeHandler.ts @@ -0,0 +1,117 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { PredictionTracker } from './predictionTracker' + +/** + * Monitors document changes in the editor and track them for prediction. + */ +export class PredictionKeyStrokeHandler { + private disposables: vscode.Disposable[] = [] + private tracker: PredictionTracker + private shadowCopies: Map = new Map() + + /** + * Creates a new PredictionKeyStrokeHandler + * @param context The extension context + * @param tracker The prediction tracker instance + * @param config Configuration options + */ + constructor(tracker: PredictionTracker) { + this.tracker = tracker + + // Initialize shadow copies for currently visible editors when extension starts + this.initializeVisibleDocuments() + + // Register event handlers + this.registerVisibleDocumentListener() + this.registerTextDocumentChangeListener() + } + + /** + * Initializes shadow copies for all currently visible text editors + */ + private initializeVisibleDocuments(): void { + const editors = vscode.window.visibleTextEditors + + for (const editor of editors) { + if (editor.document.uri.scheme === 'file') { + this.updateShadowCopy(editor.document) + } + } + } + + /** + * Registers listeners for visibility events to maintain shadow copies of document content + * Only store and update shadow copies for currently visible editors + * And remove shadow copies for files that are no longer visible + * And edits are processed only if a shadow copy exists + * This avoids the memory problem if hidden files are bulk edited, i.e. with global find/replace + */ + private registerVisibleDocumentListener(): void { + // Track when documents become visible (switched to) + const visibleDisposable = vscode.window.onDidChangeVisibleTextEditors((editors) => { + const currentVisibleFiles = new Set() + + for (const editor of editors) { + if (editor.document.uri.scheme === 'file') { + const filePath = editor.document.uri.fsPath + currentVisibleFiles.add(filePath) + this.updateShadowCopy(editor.document) + } + } + + for (const filePath of this.shadowCopies.keys()) { + if (!currentVisibleFiles.has(filePath)) { + this.shadowCopies.delete(filePath) + } + } + }) + + this.disposables.push(visibleDisposable) + } + + private updateShadowCopy(document: vscode.TextDocument): void { + if (document.uri.scheme === 'file') { + this.shadowCopies.set(document.uri.fsPath, document.getText()) + } + } + + /** + * Registers listener for text document changes to send to tracker + */ + private registerTextDocumentChangeListener(): void { + // Listen for document changes + const changeDisposable = vscode.workspace.onDidChangeTextDocument(async (event) => { + const filePath = event.document.uri.fsPath + const prevContent = this.shadowCopies.get(filePath) + + // Skip if there are no content changes or if the file is not visible + if ( + event.contentChanges.length === 0 || + event.document.uri.scheme !== 'file' || + prevContent === undefined + ) { + return + } + + await this.tracker.processEdit(event.document, prevContent) + this.updateShadowCopy(event.document) + }) + + this.disposables.push(changeDisposable) + } + + /** + * Disposes of all resources used by this handler + */ + public dispose(): void { + for (const disposable of this.disposables) { + disposable.dispose() + } + this.disposables = [] + } +} diff --git a/packages/core/src/codewhisperer/nextEditPrediction/predictionTracker.ts b/packages/core/src/codewhisperer/nextEditPrediction/predictionTracker.ts new file mode 100644 index 00000000000..1bfafb53114 --- /dev/null +++ b/packages/core/src/codewhisperer/nextEditPrediction/predictionTracker.ts @@ -0,0 +1,236 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import { getLogger } from '../../shared/logger/logger' +import * as diffGenerator from './diffContextGenerator' +import * as codewhispererClient from '../client/codewhisperer' +import { predictionTrackerDefaultConfig } from '../models/constants' +import globals from '../../shared/extensionGlobals' + +// defaul values are stored in codewhisperer/model/constants +export interface FileTrackerConfig { + maxStorageSizeKb: number + debounceIntervalMs: number + maxAgeMs: number + maxSupplementalContext: number +} + +/** + * Represents a snapshot of a file at a specific point in time + */ +export interface FileSnapshot { + filePath: string + size: number + timestamp: number + content: string +} + +export class PredictionTracker { + private snapshots: Map = new Map() + private logger = getLogger('nextEditPrediction') + readonly config: FileTrackerConfig + private storageSize: number = 0 + + constructor(extensionContext: vscode.ExtensionContext, config?: Partial) { + this.config = { + ...predictionTrackerDefaultConfig, + ...config, + } + } + + /** + * Processes an edit to a document and takes a snapshot if needed + * @param document The document being edited + * @param previousContent The content of the document before the edit + */ + public async processEdit(document: vscode.TextDocument, previousContent: string): Promise { + const filePath = document.uri.fsPath + + try { + // Get existing snapshots for this file + const fileSnapshots = this.snapshots.get(filePath) || [] + const timestamp = globals.clock.Date.now() + + // Anti-throttling, only add snap shot after the debounce is cleared + const shouldAddSnapshot = + fileSnapshots.length === 0 || + timestamp - fileSnapshots[fileSnapshots.length - 1].timestamp > this.config.debounceIntervalMs + + if (!shouldAddSnapshot) { + return + } + + const content = previousContent + const size = Buffer.byteLength(content, 'utf8') + const snapshot: FileSnapshot = { + filePath, + size, + timestamp, + content, + } + + fileSnapshots.push(snapshot) + this.snapshots.set(filePath, fileSnapshots) + this.storageSize += size + this.logger.debug( + `Snapshot taken for file: ${filePath}, total snapshots: ${this.getTotalSnapshotCount()}, total size: ${Math.round(this.storageSize / 1024)} KB` + ) + + await this.enforceMemoryLimits() + this.enforceTimeLimits(snapshot) + } catch (err) { + this.logger.error(`Failed to save snapshot: ${err}`) + } + } + + /** + * Sets up a timeout to delete the given snapshot after it exceeds the max age + */ + private enforceTimeLimits(snapshot: FileSnapshot): void { + const fileSnapshots = this.snapshots.get(snapshot.filePath) + if (fileSnapshots === undefined) { + return + } + + setTimeout(() => { + // find the snapshot and remove it + const index = fileSnapshots.indexOf(snapshot) + if (index !== -1) { + fileSnapshots.splice(index, 1) + this.storageSize -= snapshot.size + if (fileSnapshots.length === 0) { + this.snapshots.delete(snapshot.filePath) + } + this.logger.debug( + `Snapshot deleted (aged out) for file: ${snapshot.filePath}, remaining snapshots: ${this.getTotalSnapshotCount()}, new size: ${Math.round(this.storageSize / 1024)} KB` + ) + } + }, this.config.maxAgeMs) + } + + /** + * Enforces memory limits by removing old snapshots if necessary + */ + private async enforceMemoryLimits(): Promise { + while (this.storageSize > this.config.maxStorageSizeKb * 1024) { + const oldestFile = this.findOldestFile() + if (!oldestFile) { + break + } + + const fileSnapshots = this.snapshots.get(oldestFile) + if (!fileSnapshots || fileSnapshots.length === 0) { + this.snapshots.delete(oldestFile) + continue + } + + const removedSnapshot = fileSnapshots.shift() + if (removedSnapshot) { + this.storageSize -= removedSnapshot.size + this.logger.debug( + `Snapshot deleted (memory limit) for file: ${removedSnapshot.filePath}, remaining snapshots: ${this.getTotalSnapshotCount()}, new size: ${Math.round(this.storageSize / 1024)} KB` + ) + } + + if (fileSnapshots.length === 0) { + this.snapshots.delete(oldestFile) + } + } + } + + /** + * Finds the file with the oldest snapshot + * @returns The file path of the oldest snapshot + */ + private findOldestFile(): string | undefined { + let oldestTime = Number.MAX_SAFE_INTEGER + let oldestFile: string | undefined + + for (const [filePath, snapshots] of this.snapshots.entries()) { + if (snapshots.length === 0) { + continue + } + + const oldestSnapshot = snapshots[0] + if (oldestSnapshot.timestamp < oldestTime) { + oldestTime = oldestSnapshot.timestamp + oldestFile = filePath + } + } + + return oldestFile + } + + /** + * Gets all snapshots for a specific file + * @param filePath The path to the file + * @returns Array of snapshots for the file + */ + public getFileSnapshots(filePath: string): FileSnapshot[] { + return this.snapshots.get(filePath) || [] + } + + /** + * Gets all tracked files + * @returns Array of file paths + */ + public getTrackedFiles(): string[] { + return Array.from(this.snapshots.keys()) + } + + public getTotalSnapshotCount(): number { + return Array.from(this.snapshots.values()).reduce((count, snapshots) => count + snapshots.length, 0) + } + + public async getSnapshotContent(snapshot: FileSnapshot): Promise { + return snapshot.content + } + + /** + * Generates unified diffs between adjacent snapshots of a file + * and between the newest snapshot and the current file content + * + * @returns Array of SupplementalContext objects containing diffs between snapshots and current content + */ + public async generatePredictionSupplementalContext(): Promise { + try { + const activeEditor = vscode.window.activeTextEditor + if (activeEditor === undefined) { + return [] + } + const filePath = activeEditor.document.uri.fsPath + const currentContent = activeEditor.document.getText() + const snapshots = this.getFileSnapshots(filePath) + + if (snapshots.length === 0) { + return [] + } + + // Create SnapshotContent array from snapshots + const snapshotContents: diffGenerator.SnapshotContent[] = snapshots.map((snapshot) => ({ + filePath: snapshot.filePath, + content: snapshot.content, + timestamp: snapshot.timestamp, + })) + + // Use the diffGenerator module to generate supplemental contexts + return diffGenerator.generateDiffContexts( + filePath, + currentContent, + snapshotContents, + this.config.maxSupplementalContext + ) + } catch (err) { + // this ensures we are not breaking inline requests + this.logger.error(`Failed to generate prediction supplemental context: ${err}`) + return [] + } + } + + public getTotalSize() { + return this.storageSize + } +} diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index 3ad74f22d51..e965052064d 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -22,6 +22,8 @@ import { isAwsError, ToolkitError } from '../../shared/errors' import { telemetry } from '../../shared/telemetry/telemetry' import { localize } from '../../shared/utilities/vsCodeUtils' import { IAuthProvider } from '../util/authUtil' +import { Commands } from '../../shared/vscode/commands2' +import { CachedResource } from '../../shared/utilities/resourceCache' // TODO: is there a better way to manage all endpoint strings in one place? export const defaultServiceConfig: CodeWhispererConfig = { @@ -41,17 +43,41 @@ const endpoints = createConstantMap({ * 'update' -> plugin auto select the profile on users' behalf as there is only 1 profile * 'reload' -> on plugin restart, plugin will try to reload previous selected profile */ -export type ProfileSwitchIntent = 'user' | 'auth' | 'update' | 'reload' +export type ProfileSwitchIntent = 'user' | 'auth' | 'update' | 'reload' | 'customization' + +export type ProfileChangedEvent = { + profile: RegionProfile | undefined + intent: ProfileSwitchIntent +} export class RegionProfileManager { private static logger = getLogger() private _activeRegionProfile: RegionProfile | undefined - private _onDidChangeRegionProfile = new vscode.EventEmitter() + private _onDidChangeRegionProfile = new vscode.EventEmitter() public readonly onDidChangeRegionProfile = this._onDidChangeRegionProfile.event // Store the last API results (for UI propuse) so we don't need to call service again if doesn't require "latest" result private _profiles: RegionProfile[] = [] - constructor(private readonly authProvider: IAuthProvider) {} + private readonly cache = new (class extends CachedResource { + constructor(private readonly profileProvider: () => Promise) { + super( + 'aws.amazonq.regionProfiles.cache', + 60000, + { + resource: { + locked: false, + timestamp: 0, + result: undefined, + }, + }, + { timeout: 15000, interval: 1500, truthy: true } + ) + } + + override resourceProvider(): Promise { + return this.profileProvider() + } + })(this.listRegionProfile.bind(this)) get activeRegionProfile() { if (this.authProvider.isBuilderIdConnection()) { @@ -94,15 +120,23 @@ export class RegionProfileManager { return this._profiles } - async listRegionProfiles(): Promise { + constructor(private readonly authProvider: IAuthProvider) {} + + async getProfiles(): Promise { + return this.cache.getResource() + } + + async listRegionProfile(): Promise { this._profiles = [] if (!this.authProvider.isConnected() || !this.authProvider.isSsoSession()) { return [] } const availableProfiles: RegionProfile[] = [] + const failedRegions: string[] = [] + for (const [region, endpoint] of endpoints.entries()) { - const client = await this.createQClient(region, endpoint) + const client = await this._createQClient(region, endpoint) const requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) => client.listAvailableProfiles(request).promise() const request: CodeWhispererUserClient.ListAvailableProfilesRequest = {} @@ -125,13 +159,17 @@ export class RegionProfileManager { }) availableProfiles.push(...mappedPfs) + RegionProfileManager.logger.debug(`Found ${mappedPfs.length} profiles in region ${region}`) } catch (e) { const logMsg = isAwsError(e) ? `requestId=${e.requestId}; message=${e.message}` : (e as Error).message - RegionProfileManager.logger.error(`failed to listRegionProfiles: ${logMsg}`) - throw e + RegionProfileManager.logger.error(`Failed to list profiles for region ${region}: ${logMsg}`) + failedRegions.push(region) } + } - RegionProfileManager.logger.info(`available amazonq profiles: ${availableProfiles.length}`) + // Only throw error if all regions fail + if (failedRegions.length === endpoints.size) { + throw new Error(`Failed to list profiles for all regions: ${failedRegions.join(', ')}`) } this._profiles = availableProfiles @@ -148,7 +186,7 @@ export class RegionProfileManager { } // only prompt to users when users switch from A profile to B profile - if (this.activeRegionProfile !== undefined && regionProfile !== undefined) { + if (source !== 'customization' && this.activeRegionProfile !== undefined && regionProfile !== undefined) { const response = await showConfirmationMessage({ prompt: localize( 'AWS.amazonq.profile.confirmation', @@ -190,13 +228,16 @@ export class RegionProfileManager { }) } - await this._switchRegionProfile(regionProfile) + await this._switchRegionProfile(regionProfile, source) } - private async _switchRegionProfile(regionProfile: RegionProfile | undefined) { + private async _switchRegionProfile(regionProfile: RegionProfile | undefined, source: ProfileSwitchIntent) { this._activeRegionProfile = regionProfile - this._onDidChangeRegionProfile.fire(regionProfile) + this._onDidChangeRegionProfile.fire({ + profile: regionProfile, + intent: source, + }) // dont show if it's a default (fallback) if (regionProfile && this.profiles.length > 1) { void vscode.window.showInformationMessage(`You are using the ${regionProfile.name} profile for Q.`).then() @@ -204,6 +245,9 @@ export class RegionProfileManager { // persist to state await this.persistSelectRegionProfile() + + // Force status bar to reflect this change in state + await Commands.tryExecute('aws.amazonq.refreshStatusBar') } restoreProfileSelection = once(async () => { @@ -219,7 +263,7 @@ export class RegionProfileManager { return } // cross-validation - this.listRegionProfiles() + this.getProfiles() .then(async (profiles) => { const r = profiles.find((it) => it.arn === previousSelected.arn) if (!r) { @@ -279,7 +323,7 @@ export class RegionProfileManager { const selected = this.activeRegionProfile let profiles: RegionProfile[] = [] try { - profiles = await this.listRegionProfiles() + profiles = await this.getProfiles() } catch (e) { return [ { @@ -333,7 +377,24 @@ export class RegionProfileManager { return this.authProvider.isIdcConnection() && this.activeRegionProfile === undefined } - async createQClient(region: string, endpoint: string): Promise { + async clearCache() { + await this.cache.clearCache() + } + + // TODO: Should maintain sdk client in a better way + async createQClient(profile: RegionProfile): Promise { + if (!this.authProvider.isConnected() || !this.authProvider.isSsoSession()) { + throw new Error('No valid SSO connection') + } + const endpoint = endpoints.get(profile.region) + if (!endpoint) { + throw new Error(`trying to initiatize Q client with unrecognizable region ${profile.region}`) + } + return this._createQClient(profile.region, endpoint) + } + + // Visible for testing only, do not use this directly, please use createQClient(profile) + async _createQClient(region: string, endpoint: string): Promise { const token = await this.authProvider.getToken() const serviceOption: ServiceOptions = { apiConfig: userApiConfig, diff --git a/packages/core/src/codewhisperer/region/utils.ts b/packages/core/src/codewhisperer/region/utils.ts new file mode 100644 index 00000000000..dd988f74a30 --- /dev/null +++ b/packages/core/src/codewhisperer/region/utils.ts @@ -0,0 +1,49 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as nls from 'vscode-nls' +const localize = nls.loadMessageBundle() +import { AmazonQPromptSettings } from '../../shared/settings' +import { telemetry } from '../../shared/telemetry/telemetry' +import vscode from 'vscode' +import { selectRegionProfileCommand } from '../commands/basicCommands' +import { placeholder } from '../../shared/vscode/commands2' +import { toastMessage } from '../commands/types' + +/** + * Creates a toast message telling the user they need to select a Developer Profile + */ +export async function notifySelectDeveloperProfile() { + const suppressId = 'amazonQSelectDeveloperProfile' + const settings = AmazonQPromptSettings.instance + const shouldShow = settings.isPromptEnabled(suppressId) + if (!shouldShow) { + return + } + + const message = localize( + 'aws.amazonq.profile.mustSelectMessage', + 'You must select a Q Developer Profile for Amazon Q features to work.' + ) + const selectProfile = 'Select Profile' + const dontShowAgain = 'Dont Show Again' + + await telemetry.toolkit_showNotification.run(async () => { + telemetry.record({ id: 'mustSelectDeveloperProfileMessage' }) + void vscode.window.showWarningMessage(message, selectProfile, dontShowAgain).then(async (resp) => { + await telemetry.toolkit_invokeAction.run(async () => { + if (resp === selectProfile) { + // Show Profile + telemetry.record({ action: 'select' }) + void selectRegionProfileCommand.execute(placeholder, toastMessage) + } else if (resp === dontShowAgain) { + telemetry.record({ action: 'dontShowAgain' }) + await settings.disablePrompt(suppressId) + } else { + telemetry.record({ action: 'ignore' }) + } + }) + }) + }) +} diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts index c552d674bae..4c3b93425be 100644 --- a/packages/core/src/codewhisperer/service/inlineCompletionService.ts +++ b/packages/core/src/codewhisperer/service/inlineCompletionService.ts @@ -167,6 +167,9 @@ export class InlineCompletionService { /** Updates the status bar to represent the latest CW state */ refreshStatusBar() { if (AuthUtil.instance.isConnected()) { + if (AuthUtil.instance.regionProfileManager.requireProfileSelection()) { + return this.setState('needsProfile') + } return this.setState('ok') } else if (AuthUtil.instance.isConnectionExpired()) { return this.setState('expired') @@ -193,6 +196,10 @@ export class InlineCompletionService { await this.statusBar.setState('notConnected') break } + case 'needsProfile': { + await this.statusBar.setState('needsProfile') + break + } } } } @@ -203,6 +210,7 @@ const states = { ok: 'ok', expired: 'expired', notConnected: 'notConnected', + needsProfile: 'needsProfile', } as const export class CodeWhispererStatusBar { @@ -245,6 +253,7 @@ export class CodeWhispererStatusBar { statusBar.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground') break } + case 'needsProfile': case 'notConnected': statusBar.text = codicon` ${getIcon('vscode-chrome-close')} ${title}` statusBar.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground') diff --git a/packages/core/src/codewhisperer/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index 96a6d3333da..cccdacfcbd2 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -327,7 +327,7 @@ export class RecommendationHandler { msg += `\n ${index.toString().padStart(2, '0')}: ${indent(item.content, 8, true).trim()}` session.requestIdList.push(requestId) } - getLogger().debug(msg) + getLogger('nextEditPrediction').debug(`codeWhisper request ${requestId}`) if (invocationResult === 'Succeeded') { CodeWhispererCodeCoverageTracker.getTracker(session.language)?.incrementServiceInvocationCount() UserWrittenCodeTracker.instance.onQFeatureInvoked() diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts index 97dacc1a664..476123f2d6d 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformApiHandler.ts @@ -41,10 +41,10 @@ import { calculateTotalLatency } from '../../../amazonqGumby/telemetry/codeTrans import { MetadataResult } from '../../../shared/telemetry/telemetryClient' import request from '../../../shared/request' import { JobStoppedError, ZipExceedsSizeLimitError } from '../../../amazonqGumby/errors' -import { writeLogs } from './transformFileHandler' +import { createLocalBuildUploadZip, extractOriginalProjectSources, writeAndShowBuildLogs } from './transformFileHandler' import { createCodeWhispererChatStreamingClient } from '../../../shared/clients/codewhispererChatClient' import { downloadExportResultArchive } from '../../../shared/utilities/download' -import { ExportIntent, TransformationDownloadArtifactType } from '@amzn/codewhisperer-streaming' +import { ExportContext, ExportIntent, TransformationDownloadArtifactType } from '@amzn/codewhisperer-streaming' import fs from '../../../shared/fs/fs' import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' import { encodeHTML } from '../../../shared/utilities/textUtilities' @@ -52,6 +52,9 @@ import { convertToTimeString } from '../../../shared/datetime' import { getAuthType } from '../../../auth/utils' import { UserWrittenCodeTracker } from '../../tracker/userWrittenCodeTracker' import { AuthUtil } from '../../util/authUtil' +import { DiffModel } from './transformationResultsViewProvider' +import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports +import { isClientSideBuildEnabled } from '../../../dev/config' export function getSha256(buffer: Buffer) { const hasher = crypto.createHash('sha256') @@ -167,10 +170,10 @@ export async function resumeTransformationJob(jobId: string, userActionStatus: T transformationJobId: jobId, userActionStatus, // can be "COMPLETED" or "REJECTED" }) - if (response) { - // always store request ID, but it will only show up in a notification if an error occurs - return response.transformationStatus - } + getLogger().info( + `CodeTransformation: resumeTransformation API status code = ${response.$response.httpResponse.statusCode}` + ) + return response.transformationStatus } catch (e: any) { const errorMessage = `Resuming the job failed due to: ${(e as Error).message}` getLogger().error(`CodeTransformation: ResumeTransformation error = %O`, e) @@ -219,6 +222,8 @@ export async function uploadPayload( throw new Error(errorMessage) } + getLogger().info('CodeTransformation: created upload URL successfully') + try { await uploadArtifactToS3(payloadFileName, response, sha256, buffer) } catch (e: any) { @@ -251,7 +256,8 @@ export async function uploadPayload( */ const mavenExcludedExtensions = ['.repositories', '.sha1'] -const sourceExcludedExtensions = ['.DS_Store'] +// exclude .DS_Store (not relevant) and Maven executables (can cause permissions issues when building if user has not ran 'chmod') +const sourceExcludedExtensions = ['.DS_Store', 'mvnw', 'mvnw.cmd'] /** * Determines if the specified file path corresponds to a Maven metadata file @@ -360,7 +366,6 @@ export async function zipCode( sctFileName: metadataZip.getEntries().filter((entry) => entry.name.endsWith('.sct'))[0].name, }, } - // TO-DO: later consider making this add to path.join(zipManifest.dependenciesRoot, 'qct-sct-metadata', entry.entryName) so that it's more organized for (const entry of metadataZip.getEntries()) { zip.addFile(path.join(zipManifest.dependenciesRoot, entry.name), entry.getData()) } @@ -391,12 +396,21 @@ export async function zipCode( dependenciesCopied = true } + // TO-DO: decide where exactly to put the YAML file / what to name it + if (transformByQState.getCustomDependencyVersionFilePath() && zipManifest instanceof ZipManifest) { + zip.addLocalFile( + transformByQState.getCustomDependencyVersionFilePath(), + 'custom-upgrades', + 'dependency-versions.yaml' + ) + } + zip.addFile('manifest.json', Buffer.from(JSON.stringify(zipManifest)), 'utf-8') throwIfCancelled() // add text file with logs from mvn clean install and mvn copy-dependencies - logFilePath = await writeLogs() + logFilePath = await writeAndShowBuildLogs() // We don't add build-logs.txt file to the manifest if we are // uploading HIL artifacts if (!humanInTheLoopFlag) { @@ -633,16 +647,8 @@ export async function getTransformationPlan(jobId: string, profile: RegionProfil } } -export async function getTransformationSteps( - jobId: string, - handleThrottleFlag: boolean, - profile: RegionProfile | undefined -) { +export async function getTransformationSteps(jobId: string, profile: RegionProfile | undefined) { try { - // prevent ThrottlingException - if (handleThrottleFlag) { - await sleep(2000) - } const response = await codeWhisperer.codeWhispererClient.codeModernizerGetCodeTransformationPlan({ transformationJobId: jobId, profileArn: profile?.arn, @@ -683,6 +689,9 @@ export async function pollTransformationJob(jobId: string, validStates: string[] const errorMessage = response.transformationJob.reason if (errorMessage !== undefined) { + getLogger().error( + `CodeTransformation: GetTransformation returned transformation error reason = ${errorMessage}` + ) transformByQState.setJobFailureErrorChatMessage( `${CodeWhispererConstants.failedToCompleteJobGenericChatMessage} ${errorMessage}` ) @@ -693,6 +702,17 @@ export async function pollTransformationJob(jobId: string, validStates: string[] if (validStates.includes(status)) { break } + + // TO-DO: remove isClientSideBuildEnabled when releasing CSB + if ( + isClientSideBuildEnabled && + status === 'TRANSFORMING' && + transformByQState.getTransformationType() === TransformationType.LANGUAGE_UPGRADE + ) { + // client-side build is N/A for SQL conversions + await attemptLocalBuild() + } + /** * If we find a paused state, we need the user to take action. We will set the global * state for polling status and early exit. @@ -718,7 +738,111 @@ export async function pollTransformationJob(jobId: string, validStates: string[] return status } -export function getArtifactsFromProgressUpdate(progressUpdate?: TransformationProgressUpdate) { +async function attemptLocalBuild() { + const jobId = transformByQState.getJobId() + let artifactId + try { + artifactId = await getClientInstructionArtifactId(jobId) + getLogger().info(`CodeTransformation: found artifactId = ${artifactId}`) + } catch (e: any) { + // don't throw error so that we can try to get progress updates again in next polling cycle + getLogger().error(`CodeTransformation: failed to get client instruction artifact ID = %O`, e) + } + if (artifactId) { + const clientInstructionsPath = await downloadClientInstructions(jobId, artifactId) + getLogger().info( + `CodeTransformation: downloaded clientInstructions with diff.patch at: ${clientInstructionsPath}` + ) + await processClientInstructions(jobId, clientInstructionsPath, artifactId) + } +} + +async function getClientInstructionArtifactId(jobId: string) { + const steps = await getTransformationSteps(jobId, AuthUtil.instance.regionProfileManager.activeRegionProfile) + const progressUpdate = findDownloadArtifactProgressUpdate(steps) + + let artifactId = undefined + if (progressUpdate?.downloadArtifacts) { + artifactId = progressUpdate.downloadArtifacts[0].downloadArtifactId + } + return artifactId +} + +async function downloadClientInstructions(jobId: string, artifactId: string) { + const exportDestination = `downloadClientInstructions_${jobId}_${artifactId}` + const exportZipPath = path.join(os.tmpdir(), exportDestination) + + const exportContext: ExportContext = { + transformationExportContext: { + downloadArtifactType: TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS, + downloadArtifactId: artifactId, + }, + } + + await downloadAndExtractResultArchive(jobId, exportZipPath, exportContext) + return path.join(exportZipPath, 'diff.patch') +} + +async function processClientInstructions(jobId: string, clientInstructionsPath: any, artifactId: string) { + const destinationPath = path.join(os.tmpdir(), `originalCopy_${jobId}_${artifactId}`) + await extractOriginalProjectSources(destinationPath) + getLogger().info(`CodeTransformation: copied project to ${destinationPath}`) + const diffModel = new DiffModel() + diffModel.parseDiff(clientInstructionsPath, path.join(destinationPath, 'sources'), undefined, 1, true) + // show user the diff.patch + const doc = await vscode.workspace.openTextDocument(clientInstructionsPath) + await vscode.window.showTextDocument(doc, { viewColumn: vscode.ViewColumn.One }) + await runClientSideBuild(transformByQState.getProjectCopyFilePath(), artifactId) +} + +export async function runClientSideBuild(projectCopyPath: string, clientInstructionArtifactId: string) { + const baseCommand = transformByQState.getMavenName() + const args = [] + if (transformByQState.getCustomBuildCommand() === CodeWhispererConstants.skipUnitTestsBuildCommand) { + args.push('test-compile') + } else { + args.push('test') + } + const environment = { ...process.env, JAVA_HOME: transformByQState.getTargetJavaHome() } + + const argString = args.join(' ') + const spawnResult = spawnSync(baseCommand, args, { + cwd: projectCopyPath, + shell: true, + encoding: 'utf-8', + env: environment, + }) + + const buildLogs = `Intermediate build result from running ${baseCommand} ${argString}:\n\n${spawnResult.stdout}` + transformByQState.clearBuildLog() + transformByQState.appendToBuildLog(buildLogs) + await writeAndShowBuildLogs() + + const uploadZipBaseDir = path.join( + os.tmpdir(), + `clientInstructionsResult_${transformByQState.getJobId()}_${clientInstructionArtifactId}` + ) + const uploadZipPath = await createLocalBuildUploadZip(uploadZipBaseDir, spawnResult.status, spawnResult.stdout) + + // upload build results + const uploadContext: UploadContext = { + transformationUploadContext: { + jobId: transformByQState.getJobId(), + uploadArtifactType: 'ClientBuildResult', + }, + } + getLogger().info(`CodeTransformation: uploading client build results at ${uploadZipPath} and resuming job now`) + try { + await uploadPayload(uploadZipPath, AuthUtil.instance.regionProfileManager.activeRegionProfile, uploadContext) + await resumeTransformationJob(transformByQState.getJobId(), 'COMPLETED') + } finally { + await fs.delete(projectCopyPath, { recursive: true }) + await fs.delete(uploadZipBaseDir, { recursive: true }) + getLogger().info(`CodeTransformation: Just deleted project copy and uploadZipBaseDir after client-side build`) + } +} + +export function getArtifactsFromProgressUpdate(progressUpdate: TransformationProgressUpdate) { const artifactType = progressUpdate?.downloadArtifacts?.[0]?.downloadArtifactType const artifactId = progressUpdate?.downloadArtifacts?.[0]?.downloadArtifactId return { @@ -727,6 +851,16 @@ export function getArtifactsFromProgressUpdate(progressUpdate?: TransformationPr } } +// used for client-side build +export function findDownloadArtifactProgressUpdate(transformationSteps: TransformationSteps) { + return transformationSteps + .flatMap((step) => step.progressUpdates ?? []) + .find( + (update) => update.status === 'AWAITING_CLIENT_ACTION' && update.downloadArtifacts?.[0]?.downloadArtifactId + ) +} + +// used for HIL export function findDownloadArtifactStep(transformationSteps: TransformationSteps) { for (let i = 0; i < transformationSteps.length; i++) { const progressUpdates = transformationSteps[i].progressUpdates @@ -750,21 +884,23 @@ export function findDownloadArtifactStep(transformationSteps: TransformationStep } } -export async function downloadResultArchive( - jobId: string, - downloadArtifactId: string | undefined, - pathToArchive: string, - downloadArtifactType: TransformationDownloadArtifactType -) { +export async function downloadResultArchive(jobId: string, pathToArchive: string, exportContext?: ExportContext) { const cwStreamingClient = await createCodeWhispererChatStreamingClient() try { + const args = exportContext + ? { + exportId: jobId, + exportIntent: ExportIntent.TRANSFORMATION, + exportContext: exportContext, + } + : { + exportId: jobId, + exportIntent: ExportIntent.TRANSFORMATION, + } await downloadExportResultArchive( cwStreamingClient, - { - exportId: jobId, - exportIntent: ExportIntent.TRANSFORMATION, - }, + args, pathToArchive, AuthUtil.instance.regionProfileManager.activeRegionProfile ) @@ -779,9 +915,8 @@ export async function downloadResultArchive( export async function downloadAndExtractResultArchive( jobId: string, - downloadArtifactId: string | undefined, pathToArchiveDir: string, - downloadArtifactType: TransformationDownloadArtifactType + exportContext?: ExportContext ) { const archivePathExists = await fs.existsDir(pathToArchiveDir) if (!archivePathExists) { @@ -793,9 +928,10 @@ export async function downloadAndExtractResultArchive( let downloadErrorMessage = undefined try { // Download and deserialize the zip - await downloadResultArchive(jobId, downloadArtifactId, pathToArchive, downloadArtifactType) + await downloadResultArchive(jobId, pathToArchive, exportContext) const zip = new AdmZip(pathToArchive) zip.extractAllTo(pathToArchiveDir) + getLogger().info(`CodeTransformation: downloaded result archive to: ${pathToArchiveDir}`) } catch (e) { downloadErrorMessage = (e as Error).message getLogger().error(`CodeTransformation: ExportResultArchive error = %O`, e) @@ -804,12 +940,7 @@ export async function downloadAndExtractResultArchive( } export async function downloadHilResultArchive(jobId: string, downloadArtifactId: string, pathToArchiveDir: string) { - await downloadAndExtractResultArchive( - jobId, - downloadArtifactId, - pathToArchiveDir, - TransformationDownloadArtifactType.CLIENT_INSTRUCTIONS - ) + await downloadAndExtractResultArchive(jobId, pathToArchiveDir) // manifest.json // pomFolder/pom.xml or manifest has pomFolderName path diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts index c2a0617c15f..fd74ca7b147 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformFileHandler.ts @@ -9,14 +9,14 @@ import * as os from 'os' import xml2js = require('xml2js') import * as CodeWhispererConstants from '../../models/constants' import { existsSync, readFileSync, writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports -import { BuildSystem, DB, FolderInfo, transformByQState } from '../../models/model' +import { BuildSystem, DB, FolderInfo, TransformationType, transformByQState } from '../../models/model' import { IManifestFile } from '../../../amazonqFeatureDev/models' import fs from '../../../shared/fs/fs' import globals from '../../../shared/extensionGlobals' import { ChatSessionManager } from '../../../amazonqGumby/chat/storages/chatSession' import { AbsolutePathDetectedError } from '../../../amazonqGumby/errors' import { getLogger } from '../../../shared/logger/logger' -import { isWin } from '../../../shared/vscode/env' +import AdmZip from 'adm-zip' export function getDependenciesFolderInfo(): FolderInfo { const dependencyFolderName = `${CodeWhispererConstants.dependencyFolderName}${globals.clock.Date.now()}` @@ -27,12 +27,55 @@ export function getDependenciesFolderInfo(): FolderInfo { } } -export async function writeLogs() { +export async function writeAndShowBuildLogs(isLocalInstall: boolean = false) { const logFilePath = path.join(os.tmpdir(), 'build-logs.txt') - writeFileSync(logFilePath, transformByQState.getErrorLog()) + writeFileSync(logFilePath, transformByQState.getBuildLog()) + const doc = await vscode.workspace.openTextDocument(logFilePath) + if ( + !transformByQState.getBuildLog().includes('clean install succeeded') && + transformByQState.getTransformationType() !== TransformationType.SQL_CONVERSION + ) { + // only show the log if the build failed; show it in second column for intermediate builds only + const options = isLocalInstall ? undefined : { viewColumn: vscode.ViewColumn.Two } + await vscode.window.showTextDocument(doc, options) + } return logFilePath } +export async function createLocalBuildUploadZip(baseDir: string, exitCode: number | null, stdout: string) { + const manifestFilePath = path.join(baseDir, 'manifest.json') + const buildResultsManifest = { + capability: 'CLIENT_SIDE_BUILD', + exitCode: exitCode, + commandLogFileName: 'build-output.log', + } + const formattedManifest = JSON.stringify(buildResultsManifest) + await fs.writeFile(manifestFilePath, formattedManifest) + + const buildLogsFilePath = path.join(baseDir, 'build-output.log') + await fs.writeFile(buildLogsFilePath, stdout) + + const zip = new AdmZip() + zip.addLocalFile(buildLogsFilePath) + zip.addLocalFile(manifestFilePath) + + const zipPath = `${baseDir}.zip` + zip.writeZip(zipPath) + getLogger().info(`CodeTransformation: created local build upload zip at ${zipPath}`) + return zipPath +} + +// extract the 'sources' directory of the upload ZIP so that we can apply the diff.patch to a copy of the source code +export async function extractOriginalProjectSources(destinationPath: string) { + const zip = new AdmZip(transformByQState.getPayloadFilePath()) + const zipEntries = zip.getEntries() + for (const zipEntry of zipEntries) { + if (zipEntry.entryName.startsWith('sources')) { + zip.extractEntryTo(zipEntry, destinationPath, true, true) + } + } +} + export async function checkBuildSystem(projectPath: string) { const mavenBuildFilePath = path.join(projectPath, 'pom.xml') if (existsSync(mavenBuildFilePath)) { @@ -76,6 +119,17 @@ export async function parseBuildFile() { return undefined } +export async function validateCustomVersionsFile(fileContents: string) { + const requiredKeys = ['dependencyManagement:', 'identifier:', 'targetVersion:'] + for (const key of requiredKeys) { + if (!fileContents.includes(key)) { + getLogger().info(`CodeTransformation: .YAML file is missing required key: ${key}`) + return false + } + } + return true +} + export async function validateSQLMetadataFile(fileContents: string, message: any) { try { const sctData = await xml2js.parseStringPromise(fileContents) @@ -119,20 +173,10 @@ export async function validateSQLMetadataFile(fileContents: string, message: any return true } -export async function setMaven() { - let mavenWrapperExecutableName = isWin() ? 'mvnw.cmd' : 'mvnw' - const mavenWrapperExecutablePath = path.join(transformByQState.getProjectPath(), mavenWrapperExecutableName) - if (existsSync(mavenWrapperExecutablePath)) { - if (mavenWrapperExecutableName === 'mvnw') { - mavenWrapperExecutableName = './mvnw' // add the './' for non-Windows - } else if (mavenWrapperExecutableName === 'mvnw.cmd') { - mavenWrapperExecutableName = '.\\mvnw.cmd' // add the '.\' for Windows - } - transformByQState.setMavenName(mavenWrapperExecutableName) - } else { - transformByQState.setMavenName('mvn') - } - getLogger().info(`CodeTransformation: using Maven ${transformByQState.getMavenName()}`) +export function setMaven() { + // for now, just use regular Maven since the Maven executables can + // cause permissions issues when building if user has not ran 'chmod' + transformByQState.setMavenName('mvn') } export async function openBuildLogFile() { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts b/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts index dacd78f6dc3..ebcbfec8970 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts @@ -11,31 +11,29 @@ import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-i import { CodeTransformBuildCommand, telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState' import { ToolkitError } from '../../../shared/errors' -import { setMaven, writeLogs } from './transformFileHandler' +import { setMaven } from './transformFileHandler' import { throwIfCancelled } from './transformApiHandler' import { sleep } from '../../../shared/utilities/timeoutUtils' -// run 'install' with either 'mvnw.cmd', './mvnw', or 'mvn' (if wrapper exists, we use that, otherwise we use regular 'mvn') function installProjectDependencies(dependenciesFolder: FolderInfo, modulePath: string) { telemetry.codeTransform_localBuildProject.run(() => { telemetry.record({ codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId() }) - // baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn' + // will always be 'mvn' const baseCommand = transformByQState.getMavenName() - transformByQState.appendToErrorLog(`Running command ${baseCommand} clean install`) - - // Note: IntelliJ runs 'clean' separately from 'install'. Evaluate benefits (if any) of this. const args = [`-Dmaven.repo.local=${dependenciesFolder.path}`, 'clean', 'install', '-q'] + transformByQState.appendToBuildLog(`Running ${baseCommand} ${args.join(' ')}`) + if (transformByQState.getCustomBuildCommand() === CodeWhispererConstants.skipUnitTestsBuildCommand) { args.push('-DskipTests') } let environment = process.env - if (transformByQState.getJavaHome() !== undefined) { - environment = { ...process.env, JAVA_HOME: transformByQState.getJavaHome() } + if (transformByQState.getSourceJavaHome()) { + environment = { ...process.env, JAVA_HOME: transformByQState.getSourceJavaHome() } } const argString = args.join(' ') @@ -47,37 +45,27 @@ function installProjectDependencies(dependenciesFolder: FolderInfo, modulePath: maxBuffer: CodeWhispererConstants.maxBufferSize, }) - let mavenBuildCommand = transformByQState.getMavenName() - // slashes not allowed in telemetry - if (mavenBuildCommand === './mvnw') { - mavenBuildCommand = 'mvnw' - } else if (mavenBuildCommand === '.\\mvnw.cmd') { - mavenBuildCommand = 'mvnw.cmd' - } - + const mavenBuildCommand = transformByQState.getMavenName() telemetry.record({ codeTransformBuildCommand: mavenBuildCommand as CodeTransformBuildCommand }) if (spawnResult.status !== 0) { let errorLog = '' errorLog += spawnResult.error ? JSON.stringify(spawnResult.error) : '' errorLog += `${spawnResult.stderr}\n${spawnResult.stdout}` - transformByQState.appendToErrorLog(`${baseCommand} ${argString} failed: \n ${errorLog}`) + transformByQState.appendToBuildLog(`${baseCommand} ${argString} failed: \n ${errorLog}`) getLogger().error( - `CodeTransformation: Error in running Maven ${argString} command ${baseCommand} = ${errorLog}` + `CodeTransformation: Error in running Maven command ${baseCommand} ${argString} = ${errorLog}` ) throw new ToolkitError(`Maven ${argString} error`, { code: 'MavenExecutionError' }) } else { - transformByQState.appendToErrorLog(`${baseCommand} ${argString} succeeded`) + transformByQState.appendToBuildLog(`mvn clean install succeeded`) } }) } function copyProjectDependencies(dependenciesFolder: FolderInfo, modulePath: string) { - // baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn' const baseCommand = transformByQState.getMavenName() - transformByQState.appendToErrorLog(`Running command ${baseCommand} copy-dependencies`) - const args = [ 'dependency:copy-dependencies', `-DoutputDirectory=${dependenciesFolder.path}`, @@ -88,8 +76,8 @@ function copyProjectDependencies(dependenciesFolder: FolderInfo, modulePath: str ] let environment = process.env - if (transformByQState.getJavaHome() !== undefined) { - environment = { ...process.env, JAVA_HOME: transformByQState.getJavaHome() } + if (transformByQState.getSourceJavaHome()) { + environment = { ...process.env, JAVA_HOME: transformByQState.getSourceJavaHome() } } const spawnResult = spawnSync(baseCommand, args, { @@ -103,18 +91,15 @@ function copyProjectDependencies(dependenciesFolder: FolderInfo, modulePath: str let errorLog = '' errorLog += spawnResult.error ? JSON.stringify(spawnResult.error) : '' errorLog += `${spawnResult.stderr}\n${spawnResult.stdout}` - transformByQState.appendToErrorLog(`${baseCommand} copy-dependencies failed: \n ${errorLog}`) getLogger().info( - `CodeTransformation: Maven copy-dependencies command ${baseCommand} failed, but still continuing with transformation: ${errorLog}` + `CodeTransformation: Maven command ${baseCommand} ${args} failed, but still continuing with transformation: ${errorLog}` ) throw new Error('Maven copy-deps error') - } else { - transformByQState.appendToErrorLog(`${baseCommand} copy-dependencies succeeded`) } } export async function prepareProjectDependencies(dependenciesFolder: FolderInfo, rootPomPath: string) { - await setMaven() + setMaven() getLogger().info('CodeTransformation: running Maven copy-dependencies') // pause to give chat time to update await sleep(100) @@ -132,10 +117,6 @@ export async function prepareProjectDependencies(dependenciesFolder: FolderInfo, installProjectDependencies(dependenciesFolder, rootPomPath) } catch (err) { void vscode.window.showErrorMessage(CodeWhispererConstants.cleanInstallErrorNotification) - // open build-logs.txt file to show user error logs - const logFilePath = await writeLogs() - const doc = await vscode.workspace.openTextDocument(logFilePath) - await vscode.window.showTextDocument(doc) throw err } @@ -144,7 +125,7 @@ export async function prepareProjectDependencies(dependenciesFolder: FolderInfo, } export async function getVersionData() { - const baseCommand = transformByQState.getMavenName() // will be one of: 'mvnw.cmd', './mvnw', 'mvn' + const baseCommand = transformByQState.getMavenName() const projectPath = transformByQState.getProjectPath() const args = ['-v'] const spawnResult = spawnSync(baseCommand, args, { cwd: projectPath, shell: true, encoding: 'utf-8' }) @@ -174,12 +155,9 @@ export async function getVersionData() { return [localMavenVersion, localJavaVersion] } -// run maven 'versions:dependency-updates-aggregate-report' with either 'mvnw.cmd', './mvnw', or 'mvn' (if wrapper exists, we use that, otherwise we use regular 'mvn') export function runMavenDependencyUpdateCommands(dependenciesFolder: FolderInfo) { - // baseCommand will be one of: '.\mvnw.cmd', './mvnw', 'mvn' - const baseCommand = transformByQState.getMavenName() // will be one of: 'mvnw.cmd', './mvnw', 'mvn' + const baseCommand = transformByQState.getMavenName() - // Note: IntelliJ runs 'clean' separately from 'install'. Evaluate benefits (if any) of this. const args = [ 'versions:dependency-updates-aggregate-report', `-DoutputDirectory=${dependenciesFolder.path}`, @@ -188,9 +166,9 @@ export function runMavenDependencyUpdateCommands(dependenciesFolder: FolderInfo) ] let environment = process.env - // if JAVA_HOME not found or not matching project JDK, get user input for it and set here - if (transformByQState.getJavaHome() !== undefined) { - environment = { ...process.env, JAVA_HOME: transformByQState.getJavaHome() } + + if (transformByQState.getSourceJavaHome()) { + environment = { ...process.env, JAVA_HOME: transformByQState.getSourceJavaHome() } } const spawnResult = spawnSync(baseCommand, args, { diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts index 2d0585085a9..052ef53b56c 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationHubViewProvider.ts @@ -193,6 +193,8 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider return '

' case 'COMPLETED': return '

' + case 'AWAITING_CLIENT_ACTION': + return '

' case 'FAILED': default: return '

𐔧

' @@ -326,9 +328,19 @@ export class TransformationHubViewProvider implements vscode.WebviewViewProvider jobPlanProgress['generatePlan'] === StepProgress.Succeeded && transformByQState.isRunning() ) { - const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile - planSteps = await getTransformationSteps(transformByQState.getJobId(), false, profile) - transformByQState.setPlanSteps(planSteps) + try { + planSteps = await getTransformationSteps( + transformByQState.getJobId(), + AuthUtil.instance.regionProfileManager.activeRegionProfile + ) + transformByQState.setPlanSteps(planSteps) + } catch (e: any) { + // no-op; re-use current plan steps and try again in next polling cycle + getLogger().error( + `CodeTransformation: failed to get plan steps to show updates in transformation hub, continuing transformation; error = %O`, + e + ) + } } let progressHtml // for each step that has succeeded, increment activeStepId by 1 diff --git a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts index d1c6f368259..411571f0693 100644 --- a/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts +++ b/packages/core/src/codewhisperer/service/transformByQ/transformationResultsViewProvider.ts @@ -168,7 +168,8 @@ export class DiffModel { pathToDiff: string, pathToWorkspace: string, diffDescription: PatchInfo | undefined, - totalDiffPatches: number + totalDiffPatches: number, + isIntermediateBuild: boolean = false ): PatchFileNode { this.patchFileNodes = [] const diffContents = fs.readFileSync(pathToDiff, 'utf8') @@ -180,8 +181,9 @@ export class DiffModel { const changedFiles = parsePatch(diffContents) getLogger().info('CodeTransformation: parsed patch file successfully') - // path to the directory containing copy of the changed files in the transformed project - const pathToTmpSrcDir = this.copyProject(pathToWorkspace, changedFiles) + // if doing intermediate client-side build, pathToWorkspace is the path to the unzipped project's 'sources' directory (re-using upload ZIP) + // otherwise, we are at the very end of the transformation and need to copy the changed files in the project to show the diff(s) + const pathToTmpSrcDir = isIntermediateBuild ? pathToWorkspace : this.copyProject(pathToWorkspace, changedFiles) transformByQState.setProjectCopyFilePath(pathToTmpSrcDir) applyPatches(changedFiles, { diff --git a/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts b/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts index 9a4c667d7e5..1b887e587d5 100644 --- a/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts +++ b/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts @@ -21,7 +21,7 @@ import { selectRegionProfileCommand, } from '../commands/basicCommands' import { CodeWhispererCommandDeclarations } from '../commands/gettingStartedPageCommands' -import { CodeScansState, codeScanState } from '../models/model' +import { CodeScansState, codeScanState, RegionProfile } from '../models/model' import { getNewCustomizationsAvailable, getSelectedCustomization } from '../util/customizationUtil' import { cwQuickPickSource } from '../commands/types' import { AuthUtil } from '../util/authUtil' @@ -139,12 +139,16 @@ export function createSelectCustomization(): DataQuickPickItem<'selectCustomizat } as DataQuickPickItem<'selectCustomization'> } -export function createSelectRegionProfileNode(): DataQuickPickItem<'selectRegionProfile'> { - const selectedRegionProfile = AuthUtil.instance.regionProfileManager.activeRegionProfile +export function createSelectRegionProfileNode( + profile: RegionProfile | undefined +): DataQuickPickItem<'selectRegionProfile'> { + const selectedRegionProfile = profile - const label = 'Change Profile' + const label = profile ? 'Change Profile' : '(Required) Select Profile' const icon = getIcon('vscode-arrow-swap') - const description = selectedRegionProfile ? `Current profile: ${selectedRegionProfile.name}` : '' + const description = selectedRegionProfile + ? `Current profile: ${selectedRegionProfile.name}` + : 'A profile MUST be selected for features to work' return { data: 'selectRegionProfile', @@ -153,6 +157,7 @@ export function createSelectRegionProfileNode(): DataQuickPickItem<'selectRegion await selectRegionProfileCommand.execute(placeholder, cwQuickPickSource) }, description: description, + picked: profile === undefined, } } diff --git a/packages/core/src/codewhisperer/ui/statusBarMenu.ts b/packages/core/src/codewhisperer/ui/statusBarMenu.ts index ecb307d56a8..d8c05270073 100644 --- a/packages/core/src/codewhisperer/ui/statusBarMenu.ts +++ b/packages/core/src/codewhisperer/ui/statusBarMenu.ts @@ -85,7 +85,14 @@ function getAmazonQCodeWhispererNodes() { } export function getQuickPickItems(): DataQuickPickItem[] { + const isUsingEnterpriseSso = AuthUtil.instance.isIdcConnection() + const regionProfile = AuthUtil.instance.regionProfileManager.activeRegionProfile + const children = [ + // If the user has signed in but not selected a region, we strongly indicate they need to select + // a profile, otherwise features will not work. + ...(isUsingEnterpriseSso && !regionProfile ? [createSelectRegionProfileNode(undefined)] : []), + ...getAmazonQCodeWhispererNodes(), // Generic Nodes @@ -97,7 +104,7 @@ export function getQuickPickItems(): DataQuickPickItem[] { // Add settings and signout createSeparator(), createSettingsNode(), - ...(AuthUtil.instance.isIdcConnection() ? [createSelectRegionProfileNode()] : []), + ...(AuthUtil.instance.isIdcConnection() && regionProfile ? [createSelectRegionProfileNode(regionProfile)] : []), ...(AuthUtil.instance.isConnected() && !hasVendedIamCredentials() && !hasVendedCredentialsFromMetadata() ? [createSignout()] : []), diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index 629493f148a..362b1ae7157 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -26,7 +26,7 @@ import { setContext } from '../../shared/vscode/setContext' import { openUrl } from '../../shared/utilities/vsCodeUtils' import { telemetry } from '../../shared/telemetry/telemetry' import { AuthStateEvent, LanguageClientAuth, LoginTypes, SsoLogin } from '../../auth/auth2' -import { builderIdStartUrl } from '../../auth/sso/constants' +import { builderIdStartUrl, internalStartUrl } from '../../auth/sso/constants' import { VSCODE_EXTENSION_ID } from '../../shared/extensions' import { RegionProfileManager } from '../region/regionProfileManager' import { AuthFormId } from '../../login/webview/vue/types' @@ -82,12 +82,20 @@ export class AuthUtil implements IAuthProvider { }) } + // Do NOT use this in production code, only used for testing + static destroy(): void { + this.#instance = undefined as any + } + isSsoSession() { return this.session.loginType === LoginTypes.SSO } async restore() { await this.session.restore() + if (!this.isConnected()) { + await this.refreshState() + } } async login(startUrl: string, region: string) { @@ -146,6 +154,10 @@ export class AuthUtil implements IAuthProvider { return Boolean(this.connection?.startUrl && this.connection?.startUrl !== builderIdStartUrl) } + isInternalAmazonUser(): boolean { + return this.isConnected() && this.connection?.startUrl === internalStartUrl + } + onDidChangeConnectionState(handler: (e: AuthStateEvent) => any) { return this.session.onDidChangeConnectionState(handler) } @@ -247,6 +259,7 @@ export class AuthUtil implements IAuthProvider { if (state === 'expired' || state === 'notConnected') { if (this.isIdcConnection()) { await this.regionProfileManager.invalidateProfile(this.regionProfileManager.activeRegionProfile?.arn) + await this.regionProfileManager.clearCache() } this.lspAuth.deleteBearerToken() } diff --git a/packages/core/src/codewhisperer/util/codeWhispererSession.ts b/packages/core/src/codewhisperer/util/codeWhispererSession.ts index 042cd947124..17d9c998112 100644 --- a/packages/core/src/codewhisperer/util/codeWhispererSession.ts +++ b/packages/core/src/codewhisperer/util/codeWhispererSession.ts @@ -2,7 +2,6 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - import { CodewhispererCompletionType, CodewhispererLanguage, @@ -13,6 +12,7 @@ import { import { GenerateRecommendationsRequest, ListRecommendationsRequest, Recommendation } from '../client/codewhisperer' import { Position } from 'vscode' import { CodeWhispererSupplementalContext, vsCodeState } from '../models/model' +import { FileDiagnostic, getDiagnosticsOfCurrentFile } from './diagnosticsUtil' class CodeWhispererSession { static #instance: CodeWhispererSession @@ -45,6 +45,7 @@ class CodeWhispererSession { timeToFirstRecommendation = 0 firstSuggestionShowTime = 0 perceivedLatency = 0 + diagnosticsBeforeAccept: FileDiagnostic | undefined = undefined public static get instance() { return (this.#instance ??= new CodeWhispererSession()) @@ -66,6 +67,7 @@ class CodeWhispererSession { if (this.invokeSuggestionStartTime) { this.timeToFirstRecommendation = timeToFirstRecommendation - this.invokeSuggestionStartTime } + this.diagnosticsBeforeAccept = getDiagnosticsOfCurrentFile() } setSuggestionState(index: number, value: string) { @@ -116,6 +118,7 @@ class CodeWhispererSession { this.recommendations = [] this.suggestionStates.clear() this.completionTypes.clear() + this.diagnosticsBeforeAccept = undefined } } diff --git a/packages/core/src/codewhisperer/util/codewhispererSettings.ts b/packages/core/src/codewhisperer/util/codewhispererSettings.ts index c63de7aa84f..80f5d1f2a0d 100644 --- a/packages/core/src/codewhisperer/util/codewhispererSettings.ts +++ b/packages/core/src/codewhisperer/util/codewhispererSettings.ts @@ -13,6 +13,9 @@ const description = { workspaceIndexWorkerThreads: Number, workspaceIndexUseGPU: Boolean, workspaceIndexMaxSize: Number, + workspaceIndexMaxFileSize: Number, + workspaceIndexCacheDirPath: String, + workspaceIndexIgnoreFilePatterns: ArrayConstructor(String), allowFeatureDevelopmentToRunCodeAndTests: Object, ignoredSecurityIssues: ArrayConstructor(String), } @@ -55,7 +58,20 @@ export class CodeWhispererSettings extends fromExtensionManifest('amazonQ', desc public getMaxIndexSize(): number { // minimal 1MB - return Math.max(this.get('workspaceIndexMaxSize', 250), 1) + return Math.max(this.get('workspaceIndexMaxSize', 2048), 1) + } + + public getMaxIndexFileSize(): number { + // minimal 1MB + return Math.max(this.get('workspaceIndexMaxFileSize', 10), 1) + } + + public getIndexCacheDirPath(): string { + return this.get('workspaceIndexCacheDirPath', '') + } + + public getIndexIgnoreFilePatterns(): string[] { + return this.get('workspaceIndexIgnoreFilePatterns', []) } public getAutoBuildSetting(): { [key: string]: boolean } { diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index e9ec68988fd..ed3812166e4 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -10,14 +10,77 @@ import { AuthUtil } from './authUtil' import * as vscode from 'vscode' import { createCommonButtons } from '../../shared/ui/buttons' import { DataQuickPickItem, showQuickPick } from '../../shared/ui/pickerPrompter' -import { codeWhispererClient } from '../client/codewhisperer' -import { Customization, ResourceArn } from '../client/codewhispereruserclient' +import CodeWhispererUserClient, { Customization, ResourceArn } from '../client/codewhispereruserclient' import { codicon, getIcon } from '../../shared/icons' import { getLogger } from '../../shared/logger/logger' import { showMessageWithUrl } from '../../shared/utilities/messages' import { parse } from '@aws-sdk/util-arn-parser' import { Commands } from '../../shared/vscode/commands2' -import { vsCodeState } from '../models/model' +import { RegionProfile, vsCodeState } from '../models/model' +import { pageableToCollection } from '../../shared/utilities/collectionUtils' +import { isAwsError } from '../../shared/errors' +import { ProfileChangedEvent } from '../region/regionProfileManager' + +export class CustomizationProvider { + readonly region: string + constructor( + private readonly client: CodeWhispererUserClient, + private readonly profile: RegionProfile + ) { + this.region = profile.region + } + + async listAvailableCustomizations(): Promise { + const requester = async (request: CodeWhispererUserClient.ListAvailableCustomizationsRequest) => + this.client.listAvailableCustomizations(request).promise() + + try { + const request = { profileArn: this.profile.arn } + const customizations = await pageableToCollection(requester, request, 'nextToken', 'customizations') + .flatten() + .promise() + + return customizations + } catch (e) { + const logMsg = isAwsError(e) ? `requestId=${e.requestId}; message=${e.message}` : (e as Error).message + getLogger().error(`failed to listAvailableCustomizations: ${logMsg}`) + return [] + } + } + + static async init(profile: RegionProfile): Promise { + const client = await AuthUtil.instance.regionProfileManager.createQClient(profile) + return new CustomizationProvider(client, profile) + } +} + +export const onProfileChangedListener: (event: ProfileChangedEvent) => any = async (event) => { + // Skip because customization means the following validation has been done + if (event.intent === 'customization') { + return + } + const logger = getLogger() + if (!event.profile) { + await setSelectedCustomization(baseCustomization) + return + } + + // Validate user still has access to the selected customization. + const selectedCustomization = getSelectedCustomization() + // No need to validate base customization which has empty arn. + if (selectedCustomization.arn.length > 0) { + const customizationProvider = await CustomizationProvider.init(event.profile) + const customizations = await customizationProvider.listAvailableCustomizations() + + const r = customizations.find((it) => it.arn === selectedCustomization.arn) + if (!r) { + logger.debug( + `profile ${event.profile.name} doesnt have access to customization ${selectedCustomization.name} but has access to ${customizations.map((it) => it.name)}` + ) + await switchToBaseCustomizationAndNotify() + } + } +} /** * @@ -133,6 +196,9 @@ export const setSelectedCustomization = async (customization: Customization, isO } vsCodeState.isFreeTierLimitReached = false await Commands.tryExecute('aws.amazonq.refreshStatusBar') + + // hack: triggers amazon q to send the customizations back to flare + await Commands.tryExecute('aws.amazonq.updateCustomizations') } export const getPersistedCustomizations = (): Customization[] => { @@ -200,7 +266,6 @@ const createCustomizationItems = async () => { if (availableCustomizations.length === 0) { items.push(createBaseCustomizationItem()) - // TODO: finalize the url string with documentation void showMessageWithUrl( localize( 'AWS.codewhisperer.customization.noCustomizations.description', @@ -259,8 +324,12 @@ const createBaseCustomizationItem = () => { } as DataQuickPickItem } +/** + * When users click "select customizations", we're showing ALL customizations across different profiles. + * Thus If users select the customization, we also change the profile if the customization is accessible from a different profile. + */ const createCustomizationItem = ( - customization: Customization, + customization: Customization & { profile: RegionProfile }, persistedArns: (ResourceArn | undefined)[], shouldPrefixAccountId: boolean ) => { @@ -269,8 +338,8 @@ const createCustomizationItem = ( ? shouldPrefixAccountId ? accountId ? `${customization.name} (${accountId})` - : `${customization.name}` - : customization.name + : `${customization.name} (${customization.profile.name})` + : `${customization.name} (${customization.profile.name})` : 'unknown' const isNewCustomization = !persistedArns.includes(customization.arn) @@ -279,6 +348,10 @@ const createCustomizationItem = ( return { label: label, onClick: async () => { + const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile + if (profile && customization.profile.arn !== profile.arn) { + await AuthUtil.instance.regionProfileManager.switchRegionProfile(customization.profile, 'customization') + } await selectCustomization(customization) }, detail: @@ -309,13 +382,28 @@ export const selectCustomization = async (customization: Customization) => { ) } +// Return all customizations across different profiles and associate the customization with the source profile export const getAvailableCustomizationsList = async () => { - const items: Customization[] = [] - const response = await codeWhispererClient.listAvailableCustomizations() - for (const customizations of response.map( - (listAvailableCustomizationsResponse) => listAvailableCustomizationsResponse.customizations - )) { - items.push(...customizations) + const items: (Customization & { profile: RegionProfile })[] = [] + const profiles: RegionProfile[] = [] + try { + const r = await AuthUtil.instance.regionProfileManager.getProfiles() + profiles.push(...r) + } catch (e) { + getLogger().error(`Failed to list customizations because listAvailableProfiles failed %s`, (e as Error).message) + return [] + } + + for (const profile of profiles) { + const provider = await CustomizationProvider.init(profile) + const customizations = await provider.listAvailableCustomizations() + + for (const c of customizations) { + items.push({ + ...c, + profile: profile, + }) + } } return items diff --git a/packages/core/src/codewhisperer/util/diagnosticsUtil.ts b/packages/core/src/codewhisperer/util/diagnosticsUtil.ts new file mode 100644 index 00000000000..5a34c3e4430 --- /dev/null +++ b/packages/core/src/codewhisperer/util/diagnosticsUtil.ts @@ -0,0 +1,117 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' +import * as crypto from 'crypto' +import { IdeDiagnostic } from '../client/codewhispereruserclient' + +export function getDiagnosticsOfCurrentFile(): FileDiagnostic | undefined { + if (vscode.window.activeTextEditor) { + return { + diagnostics: vscode.languages.getDiagnostics(vscode.window.activeTextEditor.document.uri), + filepath: vscode.window.activeTextEditor.document.uri.fsPath, + } + } + return undefined +} + +export type FileDiagnostic = { + filepath: string + diagnostics: vscode.Diagnostic[] +} + +export function getDiagnosticsDifferences( + oldDiagnostics: FileDiagnostic | undefined, + newDiagnostics: FileDiagnostic | undefined +): { added: vscode.Diagnostic[]; removed: vscode.Diagnostic[] } { + const result: { added: vscode.Diagnostic[]; removed: vscode.Diagnostic[] } = { added: [], removed: [] } + if ( + oldDiagnostics === undefined || + newDiagnostics === undefined || + newDiagnostics.filepath !== oldDiagnostics.filepath + ) { + return result + } + + // Create maps using diagnostic key for uniqueness + const oldMap = new Map(oldDiagnostics.diagnostics.map((d) => [getDiagnosticKey(d), d])) + const newMap = new Map(newDiagnostics.diagnostics.map((d) => [getDiagnosticKey(d), d])) + + // Get added diagnostics (in new but not in old) + result.added = [...newMap.values()].filter((d) => !oldMap.has(getDiagnosticKey(d))) + + // Get removed diagnostics (in old but not in new) + result.removed = [...oldMap.values()].filter((d) => !newMap.has(getDiagnosticKey(d))) + + return result +} + +export function toIdeDiagnostics(diagnostic: vscode.Diagnostic): IdeDiagnostic { + const severity = + diagnostic.severity === vscode.DiagnosticSeverity.Error + ? 'ERROR' + : diagnostic.severity === vscode.DiagnosticSeverity.Warning + ? 'WARNING' + : diagnostic.severity === vscode.DiagnosticSeverity.Hint + ? 'HINT' + : 'INFORMATION' + + return { + ideDiagnosticType: getDiagnosticsType(diagnostic.message), + severity: severity, + source: diagnostic.source, + range: { + start: { + line: diagnostic.range.start.line, + character: diagnostic.range.start.character, + }, + end: { + line: diagnostic.range.end.line, + character: diagnostic.range.end.character, + }, + }, + } +} + +export function getDiagnosticsType(message: string): string { + const errorTypes = new Map([ + ['SYNTAX_ERROR', ['expected', 'indent', 'syntax']], + ['TYPE_ERROR', ['type', 'cast']], + ['REFERENCE_ERROR', ['undefined', 'not defined', 'undeclared', 'reference']], + ['BEST_PRACTICE', ['deprecated', 'unused', 'uninitialized', 'not initialized']], + ['SECURITY', ['security', 'vulnerability']], + ]) + + const lowercaseMessage = message.toLowerCase() + + for (const [errorType, keywords] of errorTypes) { + if (keywords.some((keyword) => lowercaseMessage.includes(keyword))) { + return errorType + } + } + + return 'OTHER' +} + +/** + * Generates a unique MD5 hash key for a VS Code diagnostic object. + * + * @param diagnostic - A VS Code Diagnostic object containing information about a code diagnostic + * @returns A 32-character hexadecimal MD5 hash string that uniquely identifies the diagnostic + * + * @description + * Creates a deterministic hash by combining the diagnostic's message, severity, code, and source. + * This hash can be used as a unique identifier for deduplication or tracking purposes. + * Note: range is not in the hashed string because a diagnostic can move and its range can change within the editor + */ +function getDiagnosticKey(diagnostic: vscode.Diagnostic): string { + const jsonStr = JSON.stringify({ + message: diagnostic.message, + severity: diagnostic.severity, + code: diagnostic.code, + source: diagnostic.source, + }) + + return crypto.createHash('md5').update(jsonStr).digest('hex') +} diff --git a/packages/core/src/codewhisperer/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts index 88c3d3847f1..a3f787af6c6 100644 --- a/packages/core/src/codewhisperer/util/editorContext.ts +++ b/packages/core/src/codewhisperer/util/editorContext.ts @@ -8,10 +8,11 @@ import * as codewhispererClient from '../client/codewhisperer' import * as path from 'path' import * as CodeWhispererConstants from '../models/constants' import { getTabSizeSetting } from '../../shared/utilities/editorUtilities' +import { truncate } from '../../shared/utilities/textUtilities' import { getLogger } from '../../shared/logger/logger' import { runtimeLanguageContext } from './runtimeLanguageContext' import { fetchSupplementalContext } from './supplementalContext/supplementalContextUtil' -import { supplementalContextTimeoutInMs } from '../models/constants' +import { editorStateMaxLength, supplementalContextTimeoutInMs } from '../models/constants' import { getSelectedCustomization } from './customizationUtil' import { selectFrom } from '../../shared/utilities/tsUtils' import { checkLeftContextKeywordsForJson } from './commonUtil' @@ -20,22 +21,129 @@ import { getOptOutPreference } from '../../shared/telemetry/util' import { indent } from '../../shared/utilities/textUtilities' import { isInDirectory } from '../../shared/filesystemUtilities' import { AuthUtil } from './authUtil' +import { predictionTracker } from '../nextEditPrediction/activation' let tabSize: number = getTabSizeSetting() +function getEnclosingNotebook(editor: vscode.TextEditor): vscode.NotebookDocument | undefined { + // For notebook cells, find the existing notebook with a cell that matches the current editor. + return vscode.workspace.notebookDocuments.find( + (nb) => + nb.notebookType === 'jupyter-notebook' && nb.getCells().some((cell) => cell.document === editor.document) + ) +} + +export function getNotebookContext( + notebook: vscode.NotebookDocument, + editor: vscode.TextEditor, + languageName: string, + caretLeftFileContext: string, + caretRightFileContext: string +) { + // Expand the context for a cell inside of a noteboo with whatever text fits from the preceding and subsequent cells + const allCells = notebook.getCells() + const cellIndex = allCells.findIndex((cell) => cell.document === editor.document) + // Extract text from prior cells if there is enough room in left file context + if (caretLeftFileContext.length < CodeWhispererConstants.charactersLimit - 1) { + const leftCellsText = getNotebookCellsSliceContext( + allCells.slice(0, cellIndex), + CodeWhispererConstants.charactersLimit - (caretLeftFileContext.length + 1), + languageName, + true + ) + if (leftCellsText.length > 0) { + caretLeftFileContext = addNewlineIfMissing(leftCellsText) + caretLeftFileContext + } + } + // Extract text from subsequent cells if there is enough room in right file context + if (caretRightFileContext.length < CodeWhispererConstants.charactersLimit - 1) { + const rightCellsText = getNotebookCellsSliceContext( + allCells.slice(cellIndex + 1), + CodeWhispererConstants.charactersLimit - (caretRightFileContext.length + 1), + languageName, + false + ) + if (rightCellsText.length > 0) { + caretRightFileContext = addNewlineIfMissing(caretRightFileContext) + rightCellsText + } + } + return { caretLeftFileContext, caretRightFileContext } +} + +export function getNotebookCellContext(cell: vscode.NotebookCell, referenceLanguage?: string): string { + // Extract the text verbatim if the cell is code and the cell has the same language. + // Otherwise, add the correct comment string for the reference language + const cellText = cell.document.getText() + if ( + cell.kind === vscode.NotebookCellKind.Markup || + (runtimeLanguageContext.normalizeLanguage(cell.document.languageId) ?? cell.document.languageId) !== + referenceLanguage + ) { + const commentPrefix = runtimeLanguageContext.getSingleLineCommentPrefix(referenceLanguage) + if (commentPrefix === '') { + return cellText + } + return cell.document + .getText() + .split('\n') + .map((line) => `${commentPrefix}${line}`) + .join('\n') + } + return cellText +} + +export function getNotebookCellsSliceContext( + cells: vscode.NotebookCell[], + maxLength: number, + referenceLanguage: string, + fromStart: boolean +): string { + // Extract context from array of notebook cells that fits inside `maxLength` characters, + // from either the start or the end of the array. + let output: string[] = [] + if (!fromStart) { + cells = cells.reverse() + } + cells.some((cell) => { + const cellText = addNewlineIfMissing(getNotebookCellContext(cell, referenceLanguage)) + if (cellText.length > 0) { + if (cellText.length >= maxLength) { + if (fromStart) { + output.push(cellText.substring(0, maxLength)) + } else { + output.push(cellText.substring(cellText.length - maxLength)) + } + return true + } + output.push(cellText) + maxLength -= cellText.length + } + }) + if (!fromStart) { + output = output.reverse() + } + return output.join('') +} + +export function addNewlineIfMissing(text: string): string { + if (text.length > 0 && !text.endsWith('\n')) { + text += '\n' + } + return text +} + export function extractContextForCodeWhisperer(editor: vscode.TextEditor): codewhispererClient.FileContext { const document = editor.document const curPos = editor.selection.active const offset = document.offsetAt(curPos) - const caretLeftFileContext = editor.document.getText( + let caretLeftFileContext = editor.document.getText( new vscode.Range( document.positionAt(offset - CodeWhispererConstants.charactersLimit), document.positionAt(offset) ) ) - - const caretRightFileContext = editor.document.getText( + let caretRightFileContext = editor.document.getText( new vscode.Range( document.positionAt(offset), document.positionAt(offset + CodeWhispererConstants.charactersLimit) @@ -46,6 +154,19 @@ export function extractContextForCodeWhisperer(editor: vscode.TextEditor): codew languageName = runtimeLanguageContext.normalizeLanguage(editor.document.languageId) ?? editor.document.languageId } + if (editor.document.uri.scheme === 'vscode-notebook-cell') { + const notebook = getEnclosingNotebook(editor) + if (notebook) { + ;({ caretLeftFileContext, caretRightFileContext } = getNotebookContext( + notebook, + editor, + languageName, + caretLeftFileContext, + caretRightFileContext + )) + } + } + return { filename: getFileRelativePath(editor), programmingLanguage: { @@ -119,8 +240,14 @@ export async function buildListRecommendationRequest( logSupplementalContext(supplementalContexts) + // Get predictionSupplementalContext from PredictionTracker + let predictionSupplementalContext: codewhispererClient.SupplementalContext[] = [] + if (predictionTracker) { + predictionSupplementalContext = await predictionTracker.generatePredictionSupplementalContext() + } + const selectedCustomization = getSelectedCustomization() - const supplementalContext: codewhispererClient.SupplementalContext[] = supplementalContexts + const completionSupplementalContext: codewhispererClient.SupplementalContext[] = supplementalContexts ? supplementalContexts.supplementalContextItems.map((v) => { return selectFrom(v, 'content', 'filePath') }) @@ -128,6 +255,10 @@ export async function buildListRecommendationRequest( const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile + const editorState = getEditorState(editor, fileContext) + + // Combine inline and prediction supplemental contexts + const finalSupplementalContext = completionSupplementalContext.concat(predictionSupplementalContext) return { request: { fileContext: fileContext, @@ -135,7 +266,9 @@ export async function buildListRecommendationRequest( referenceTrackerConfiguration: { recommendationsWithReferences: allowCodeWithReference ? 'ALLOW' : 'BLOCK', }, - supplementalContexts: supplementalContext, + supplementalContexts: finalSupplementalContext, + editorState: editorState, + maxResults: CodeWhispererConstants.maxRecommendations, customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, optOutPreference: getOptOutPreference(), workspaceId: await getWorkspaceId(editor), @@ -201,6 +334,45 @@ export function getTabSize(): number { return tabSize } +export function getEditorState(editor: vscode.TextEditor, fileContext: codewhispererClient.FileContext): any { + try { + const cursorPosition = editor.selection.active + const cursorOffset = editor.document.offsetAt(cursorPosition) + const documentText = editor.document.getText() + + // Truncate if document content is too large (defined in constants.ts) + let fileText = documentText + if (documentText.length > editorStateMaxLength) { + const halfLength = Math.floor(editorStateMaxLength / 2) + + // Use truncate function to get the text around the cursor position + const leftPart = truncate(documentText.substring(0, cursorOffset), -halfLength, '') + const rightPart = truncate(documentText.substring(cursorOffset), halfLength, '') + + fileText = leftPart + rightPart + } + + return { + document: { + programmingLanguage: { + languageName: fileContext.programmingLanguage.languageName, + }, + relativeFilePath: fileContext.filename, + text: fileText, + }, + cursorState: { + position: { + line: editor.selection.active.line, + character: editor.selection.active.character, + }, + }, + } + } catch (error) { + getLogger().error(`Error generating editor state: ${error}`) + return undefined + } +} + export function getLeftContext(editor: vscode.TextEditor, line: number): string { let lineText = '' try { diff --git a/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts b/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts index 9a495cf5356..3a1403b453e 100644 --- a/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts +++ b/packages/core/src/codewhisperer/util/runtimeLanguageContext.ts @@ -58,6 +58,13 @@ export class RuntimeLanguageContext { */ private supportedLanguageExtensionMap: ConstantMap + /** + * A map storing single-line comment prefixes for different languages + * Key: CodewhispererLanguage + * Value: Comment prefix string + */ + private languageSingleLineCommentPrefixMap: ConstantMap + constructor() { this.supportedLanguageMap = createConstantMap< CodeWhispererConstants.PlatformLanguageId | CodewhispererLanguage, @@ -146,6 +153,39 @@ export class RuntimeLanguageContext { psm1: 'powershell', r: 'r', }) + this.languageSingleLineCommentPrefixMap = createConstantMap({ + c: '// ', + cpp: '// ', + csharp: '// ', + dart: '// ', + go: '// ', + hcl: '# ', + java: '// ', + javascript: '// ', + json: '// ', + jsonc: '// ', + jsx: '// ', + kotlin: '// ', + lua: '-- ', + php: '// ', + plaintext: '', + powershell: '# ', + python: '# ', + r: '# ', + ruby: '# ', + rust: '// ', + scala: '// ', + shell: '# ', + sql: '-- ', + swift: '// ', + systemVerilog: '// ', + tf: '# ', + tsx: '// ', + typescript: '// ', + vue: '', // vue lacks a single-line comment prefix + yaml: '# ', + yml: '# ', + }) } /** @@ -159,6 +199,16 @@ export class RuntimeLanguageContext { return this.supportedLanguageMap.get(languageId) } + /** + * Get the comment prefix for a given language + * @param language The language to get comment prefix for + * @returns The comment prefix string, or empty string if not found + */ + public getSingleLineCommentPrefix(language?: string): string { + const normalizedLanguage = this.normalizeLanguage(language) + return normalizedLanguage ? (this.languageSingleLineCommentPrefixMap.get(normalizedLanguage) ?? '') : '' + } + /** * Normalize client side language id to service aware language id (service is not aware of jsx/tsx) * Only used when invoking CodeWhisperer service API, for client usage please use normalizeLanguage diff --git a/packages/core/src/codewhisperer/util/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 2bc173b97a9..bb4e62cfaba 100644 --- a/packages/core/src/codewhisperer/util/telemetryHelper.ts +++ b/packages/core/src/codewhisperer/util/telemetryHelper.ts @@ -26,9 +26,12 @@ import { getLogger } from '../../shared/logger/logger' import { session } from './codeWhispererSession' import { CodeWhispererSupplementalContext } from '../models/model' import { FeatureConfigProvider } from '../../shared/featureConfig' -import { CodeScanRemediationsEventType } from '../client/codewhispereruserclient' +import CodeWhispererUserClient, { CodeScanRemediationsEventType } from '../client/codewhispereruserclient' import { CodeAnalysisScope as CodeAnalysisScopeClientSide } from '../models/constants' import { Session } from '../../amazonqTest/chat/session/session' +import { sleep } from '../../shared/utilities/timeoutUtils' +import { getDiagnosticsDifferences, getDiagnosticsOfCurrentFile, toIdeDiagnostics } from './diagnosticsUtil' +import { Auth } from '../../auth/auth' export class TelemetryHelper { // Some variables for client component latency @@ -422,46 +425,56 @@ export class TelemetryHelper { e2eLatency = 0.0 } - client - .sendTelemetryEvent({ - telemetryEvent: { - userTriggerDecisionEvent: { - sessionId: sessionId, - requestId: this.sessionDecisions[0].codewhispererFirstRequestId, - customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, - programmingLanguage: { - languageName: runtimeLanguageContext.toRuntimeLanguage( - this.sessionDecisions[0].codewhispererLanguage - ), - }, - completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType), - suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState), - recommendationLatencyMilliseconds: e2eLatency, - triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, - perceivedLatencyMilliseconds: session.perceivedLatency, - timestamp: new Date(Date.now()), - suggestionReferenceCount: referenceCount, - generatedLine: generatedLines, - numberOfRecommendations: suggestionCount, - acceptedCharacterCount: acceptedRecommendationContent.length, - }, - }, - profileArn: profile?.arn, - }) - .then() - .catch((error) => { - let requestId: string | undefined - if (isAwsError(error)) { - requestId = error.requestId - } + const userTriggerDecisionEvent: CodeWhispererUserClient.UserTriggerDecisionEvent = { + sessionId: sessionId, + requestId: this.sessionDecisions[0].codewhispererFirstRequestId, + customizationArn: selectedCustomization.arn === '' ? undefined : selectedCustomization.arn, + programmingLanguage: { + languageName: runtimeLanguageContext.toRuntimeLanguage(this.sessionDecisions[0].codewhispererLanguage), + }, + completionType: this.getSendTelemetryCompletionType(aggregatedCompletionType), + suggestionState: this.getSendTelemetrySuggestionState(aggregatedSuggestionState), + recommendationLatencyMilliseconds: e2eLatency, + triggerToResponseLatencyMilliseconds: session.timeToFirstRecommendation, + perceivedLatencyMilliseconds: session.perceivedLatency, + timestamp: new Date(Date.now()), + suggestionReferenceCount: referenceCount, + generatedLine: generatedLines, + numberOfRecommendations: suggestionCount, + acceptedCharacterCount: acceptedRecommendationContent.length, + } + this.resetUserTriggerDecisionTelemetry() - getLogger().debug( - `Failed to sendTelemetryEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${ - error.message - }` + const sendEvent = () => + client + .sendTelemetryEvent({ + telemetryEvent: { userTriggerDecisionEvent: userTriggerDecisionEvent }, + profileArn: profile?.arn, + }) + .catch((error) => { + const requestId = isAwsError(error) ? error.requestId : undefined + getLogger().debug( + `Failed to sendTelemetryEvent to CodeWhisperer, requestId: ${requestId ?? ''}, message: ${error.message}` + ) + }) + + if (userTriggerDecisionEvent.suggestionState === 'ACCEPT' && Auth.instance.isInternalAmazonUser()) { + // wait 1 seconds for the user installed 3rd party LSP + // to update its diagnostics. + void sleep(1000).then(() => { + const diagnosticDiff = getDiagnosticsDifferences( + session.diagnosticsBeforeAccept, + getDiagnosticsOfCurrentFile() + ) + userTriggerDecisionEvent.addedIdeDiagnostics = diagnosticDiff.added.map((it) => toIdeDiagnostics(it)) + userTriggerDecisionEvent.removedIdeDiagnostics = diagnosticDiff.removed.map((it) => + toIdeDiagnostics(it) ) + void sendEvent() }) - this.resetUserTriggerDecisionTelemetry() + } else { + void sendEvent() + } } public getLastTriggerDecisionForClassifier() { diff --git a/packages/core/src/codewhispererChat/app.ts b/packages/core/src/codewhispererChat/app.ts index 63147002339..3916e571956 100644 --- a/packages/core/src/codewhispererChat/app.ts +++ b/packages/core/src/codewhispererChat/app.ts @@ -236,5 +236,5 @@ export function init(appContext: AmazonQAppInitContext) { appContext.registerWebViewToAppMessagePublisher(new MessagePublisher(cwChatUIInputEventEmitter), 'cwc') - registerCommands(cwChatControllerMessagePublishers) + registerCommands() } diff --git a/packages/core/src/codewhispererChat/commands/registerCommands.ts b/packages/core/src/codewhispererChat/commands/registerCommands.ts index 39d8383c867..8ec1f7ac759 100644 --- a/packages/core/src/codewhispererChat/commands/registerCommands.ts +++ b/packages/core/src/codewhispererChat/commands/registerCommands.ts @@ -6,7 +6,6 @@ import { commandPalette } from '../../codewhisperer/commands/types' import { CodeScanIssue } from '../../codewhisperer/models/model' import { Commands, VsCodeCommandArg, placeholder } from '../../shared/vscode/commands2' -import { ChatControllerMessagePublishers } from '../controllers/chat/controller' /** * Opens the Amazon Q panel, showing the correct View that should @@ -37,73 +36,11 @@ export const focusAmazonQPanelKeybinding = Commands.declare('_aws.amazonq.focusC await focusAmazonQPanel.execute(placeholder, 'keybinding') }) -const getCommandTriggerType = (data: any): EditorContextCommandTriggerType => { - // data is undefined when commands triggered from keybinding or command palette. Currently no - // way to differentiate keybinding and command palette, so both interactions are recorded as keybinding - return data === undefined ? 'keybinding' : 'contextMenu' -} - -export function registerCommands(controllerPublishers: ChatControllerMessagePublishers) { - Commands.register('aws.amazonq.explainCode', async (data) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.explainCode').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.explainCode', - triggerType: getCommandTriggerType(data), - }) - }) - }) - Commands.register('aws.amazonq.refactorCode', async (data) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.refactorCode').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.refactorCode', - triggerType: getCommandTriggerType(data), - }) - }) - }) - Commands.register('aws.amazonq.fixCode', async (data) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.fixCode').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.fixCode', - triggerType: getCommandTriggerType(data), - }) - }) - }) - Commands.register('aws.amazonq.optimizeCode', async (data) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.optimizeCode').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.optimizeCode', - triggerType: getCommandTriggerType(data), - }) - }) - }) - Commands.register('aws.amazonq.sendToPrompt', async (data) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.sendToPrompt').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.sendToPrompt', - triggerType: getCommandTriggerType(data), - }) - }) - }) - Commands.register('aws.amazonq.explainIssue', async (issue) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.explainIssue').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.explainIssue', - triggerType: 'click', - issue, - }) - }) - }) - Commands.register('aws.amazonq.generateUnitTests', async (data) => { - return focusAmazonQPanel.execute(placeholder, 'amazonq.generateUnitTests').then(() => { - controllerPublishers.processContextMenuCommand.publish({ - type: 'aws.amazonq.generateUnitTests', - triggerType: getCommandTriggerType(data), - }) - }) - }) - Commands.register('aws.amazonq.updateContextCommandItems', () => { - controllerPublishers.processContextCommandUpdateMessage.publish() - }) +export function registerCommands() { + /** + * make these no-ops, since theres still callers that need to be deprecated + */ + Commands.register('aws.amazonq.updateContextCommandItems', () => {}) } export type EditorContextBaseCommandType = diff --git a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index 798f9c0b84a..292d00f46fc 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts @@ -36,7 +36,9 @@ import globals from '../../../shared/extensionGlobals' import { getLogger } from '../../../shared/logger/logger' import { codeWhispererClient } from '../../../codewhisperer/client/codewhisperer' import { isAwsError } from '../../../shared/errors' -import { ChatMessageInteractionType } from '../../../codewhisperer/client/codewhispereruserclient' +import CodeWhispererUserClient, { + ChatMessageInteractionType, +} from '../../../codewhisperer/client/codewhispereruserclient' import { supportedLanguagesList } from '../chat/chatRequest/converter' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' @@ -44,6 +46,14 @@ import { undefinedIfEmpty } from '../../../shared/utilities/textUtilities' import { AdditionalContextPrompt } from '../../../amazonq/lsp/types' import { getUserPromptsDirectory, promptFileExtension } from '../../constants' import { isInDirectory } from '../../../shared/filesystemUtilities' +import { sleep } from '../../../shared/utilities/timeoutUtils' +import { + FileDiagnostic, + getDiagnosticsDifferences, + getDiagnosticsOfCurrentFile, + toIdeDiagnostics, +} from '../../../codewhisperer/util/diagnosticsUtil' +import { Auth } from '../../../auth/auth' export function logSendTelemetryEventFailure(error: any) { let requestId: string | undefined @@ -92,11 +102,17 @@ export class CWCTelemetryHelper { } > = new Map() + private documentDiagnostics: FileDiagnostic | undefined = undefined + constructor(sessionStorage: ChatSessionStorage, triggerEventsStorage: TriggerEventsStorage) { this.sessionStorage = sessionStorage this.triggerEventsStorage = triggerEventsStorage } + public setDocumentDiagnostics() { + this.documentDiagnostics = getDiagnosticsOfCurrentFile() + } + public static init(sessionStorage: ChatSessionStorage, triggerEventsStorage: TriggerEventsStorage) { const lastInstance = CWCTelemetryHelper.instance if (lastInstance !== undefined) { @@ -359,26 +375,50 @@ export class CWCTelemetryHelper { } telemetry.amazonq_interactWithMessage.emit({ ...event, ...additionalContextInfo }) - codeWhispererClient - .sendTelemetryEvent({ - telemetryEvent: { - chatInteractWithMessageEvent: { - conversationId: event.cwsprChatConversationId, - messageId: event.cwsprChatMessageId, - interactionType: this.getCWClientTelemetryInteractionType(event.cwsprChatInteractionType), - interactionTarget: event.cwsprChatInteractionTarget, - acceptedCharacterCount: event.cwsprChatAcceptedCharactersLength, - acceptedLineCount: event.cwsprChatAcceptedNumberOfLines, - acceptedSnippetHasReference: false, - hasProjectLevelContext: this.responseWithContextInfo.get(event.cwsprChatMessageId) - ?.cwsprChatHasProjectContext, - customizationArn: undefinedIfEmpty(getSelectedCustomization().arn), - }, - }, - profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, + const interactWithMessageEvent: CodeWhispererUserClient.ChatInteractWithMessageEvent = { + conversationId: event.cwsprChatConversationId, + messageId: event.cwsprChatMessageId, + interactionType: this.getCWClientTelemetryInteractionType(event.cwsprChatInteractionType), + interactionTarget: event.cwsprChatInteractionTarget, + acceptedCharacterCount: event.cwsprChatAcceptedCharactersLength, + acceptedLineCount: event.cwsprChatAcceptedNumberOfLines, + acceptedSnippetHasReference: false, + hasProjectLevelContext: this.responseWithContextInfo.get(event.cwsprChatMessageId) + ?.cwsprChatHasProjectContext, + customizationArn: undefinedIfEmpty(getSelectedCustomization().arn), + } + if (interactWithMessageEvent.interactionType === 'INSERT_AT_CURSOR' && Auth.instance.isInternalAmazonUser()) { + // wait 1 seconds for the user installed 3rd party LSP + // to update its diagnostics. + void sleep(1000).then(() => { + const diagnosticDiff = getDiagnosticsDifferences( + this.documentDiagnostics, + getDiagnosticsOfCurrentFile() + ) + interactWithMessageEvent.addedIdeDiagnostics = diagnosticDiff.added.map((it) => toIdeDiagnostics(it)) + interactWithMessageEvent.removedIdeDiagnostics = diagnosticDiff.removed.map((it) => + toIdeDiagnostics(it) + ) + codeWhispererClient + .sendTelemetryEvent({ + telemetryEvent: { + chatInteractWithMessageEvent: interactWithMessageEvent, + }, + }) + .then() + .catch(logSendTelemetryEventFailure) }) - .then() - .catch(logSendTelemetryEventFailure) + } else { + codeWhispererClient + .sendTelemetryEvent({ + telemetryEvent: { + chatInteractWithMessageEvent: interactWithMessageEvent, + }, + profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, + }) + .then() + .catch(logSendTelemetryEventFailure) + } } private getCWClientTelemetryInteractionType(type: CwsprChatInteractionType): ChatMessageInteractionType { diff --git a/packages/core/src/dev/config.ts b/packages/core/src/dev/config.ts index d5fa49b2426..b4df78f64b0 100644 --- a/packages/core/src/dev/config.ts +++ b/packages/core/src/dev/config.ts @@ -10,3 +10,6 @@ export const betaUrl = { amazonq: '', toolkit: '', } + +// TO-DO: remove when releasing CSB +export const isClientSideBuildEnabled = false diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue b/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue index 6d64291cff6..e7d09c584c9 100644 --- a/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue +++ b/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue @@ -139,6 +139,10 @@ runtime in data: {{ launchConfig.lambda.runtime }} +
+ + +
@@ -195,6 +199,10 @@ For invoke the runtime defined in the template is used.

+
+ + +

@@ -229,6 +237,10 @@ runtime in data: {{ launchConfig.lambda.runtime }}
+
+ + +
diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts b/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts index 6502accae41..375e22f4878 100644 --- a/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts +++ b/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts @@ -48,6 +48,7 @@ interface SamInvokeVueData { showNameInput: boolean newTestEventName: string resourceData: ResourceData | undefined + useDebugger: boolean } function newLaunchConfig(existingConfig?: AwsSamDebuggerConfiguration): AwsSamDebuggerConfigurationLoose { @@ -112,6 +113,7 @@ function initData() { return { containerBuild: false, skipNewImageCheck: false, + useDebugger: true, launchConfig: newLaunchConfig(), payload: { value: '', errorMsg: '' }, apiPayload: { value: '', errorMsg: '' }, @@ -449,6 +451,7 @@ export default defineComponent({ }, } : undefined, + noDebug: !this.useDebugger, } }, clearForm() { diff --git a/packages/core/src/login/webview/index.ts b/packages/core/src/login/webview/index.ts index 80abcc4fd79..d7760c89d4c 100644 --- a/packages/core/src/login/webview/index.ts +++ b/packages/core/src/login/webview/index.ts @@ -5,3 +5,4 @@ export { CommonAuthViewProvider } from './commonAuthViewProvider' export { CommonAuthWebview } from './vue/backend' +export * as backendAmazonQ from './vue/amazonq/backend_amazonq' diff --git a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts index fd558e546d6..0a9dd576d6f 100644 --- a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts +++ b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as vscode from 'vscode' -import { AwsConnection, SsoConnection, getTelemetryMetadataForConn } from '../../../../auth/connection' +import { AwsConnection, SsoConnection } from '../../../../auth/connection' import { AuthUtil } from '../../../../codewhisperer/util/authUtil' import { CommonAuthWebview } from '../backend' import { awsIdSignIn } from '../../../../codewhisperer/util/showSsoPrompt' @@ -73,6 +73,10 @@ export class AmazonQLoginWebview extends CommonAuthWebview { async startEnterpriseSetup(startUrl: string, region: string): Promise { getLogger().debug(`called startEnterpriseSetup() with startUrl: '${startUrl}', region: '${region}'`) + await globals.globalState.update('recentSso', { + startUrl: startUrl, + region: region, + }) return await this.ssoSetup('startCodeWhispererEnterpriseSetup', async () => { this.storeMetricMetadata({ credentialStartUrl: startUrl, @@ -114,9 +118,7 @@ export class AmazonQLoginWebview extends CommonAuthWebview { ...(await AuthUtil.instance.getTelemetryMetadata()), }) await AuthUtil.instance.reauthenticate() - this.storeMetricMetadata({ - ...(await AuthUtil.instance.getTelemetryMetadata()), - }) + this.storeMetricMetadata(await AuthUtil.instance.getTelemetryMetadata()) }) } finally { this.isReauthenticating = false @@ -178,7 +180,7 @@ export class AmazonQLoginWebview extends CommonAuthWebview { this.storeMetricMetadata({ authEnabledFeatures: 'codewhisperer', isReAuth: true, - ...(await getTelemetryMetadataForConn()), + ...(await AuthUtil.instance.getTelemetryMetadata()), result: 'Cancelled', }) @@ -211,7 +213,7 @@ export class AmazonQLoginWebview extends CommonAuthWebview { */ override async listRegionProfiles(): Promise { try { - return await AuthUtil.instance.regionProfileManager.listRegionProfiles() + return await AuthUtil.instance.regionProfileManager.getProfiles() } catch (e) { telemetry.amazonq_didSelectProfile.emit({ source: 'auth', diff --git a/packages/core/src/login/webview/vue/backend.ts b/packages/core/src/login/webview/vue/backend.ts index 3fe38522b78..edb1980a8c0 100644 --- a/packages/core/src/login/webview/vue/backend.ts +++ b/packages/core/src/login/webview/vue/backend.ts @@ -287,8 +287,13 @@ export abstract class CommonAuthWebview extends VueWebview { return authEnabledFeatures.join(',') } - getDefaultStartUrl() { - return DevSettings.instance.get('autofillStartUrl', '') + getDefaultSsoProfile(): { startUrl: string; region: string } { + const devSettings = DevSettings.instance.get('autofillStartUrl', '') + if (devSettings) { + return { startUrl: devSettings, region: 'us-east-1' } + } + + return globals.globalState.tryGet('recentSso', Object, { startUrl: '', region: 'us-east-1' }) } cancelAuthFlow() { diff --git a/packages/core/src/login/webview/vue/login.vue b/packages/core/src/login/webview/vue/login.vue index 50b1e244043..ddcd1d91c28 100644 --- a/packages/core/src/login/webview/vue/login.vue +++ b/packages/core/src/login/webview/vue/login.vue @@ -343,7 +343,7 @@ export default defineComponent({ regions: [] as Region[], startUrlError: '', startUrlWarning: '', - selectedRegion: 'us-east-1', + selectedRegion: '', startUrl: '', app: this.app, LoginOption, @@ -353,7 +353,9 @@ export default defineComponent({ } }, async created() { - this.startUrl = await this.getDefaultStartUrl() + const defaultSso = await this.getDefaultSso() + this.startUrl = defaultSso.startUrl + this.selectedRegion = defaultSso.region await this.emitUpdate('created') }, @@ -564,8 +566,8 @@ export default defineComponent({ async updateExistingStartUrls() { this.existingStartUrls = (await client.listSsoConnections()).map((conn) => conn.startUrl) }, - async getDefaultStartUrl() { - return await client.getDefaultStartUrl() + async getDefaultSso() { + return await client.getDefaultSsoProfile() }, handleHelpLinkClick() { void client.emitUiClick('auth_helpLink') diff --git a/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts b/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts index edf3e8c775e..caec2c764bc 100644 --- a/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts +++ b/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts @@ -22,6 +22,7 @@ import { setContext } from '../../../../shared/vscode/setContext' import { builderIdStartUrl } from '../../../../auth/sso/constants' import { RegionProfile } from '../../../../codewhisperer/models/model' import { ProfileSwitchIntent } from '../../../../codewhisperer/region/regionProfileManager' +import globals from '../../../../shared/extensionGlobals' export class ToolkitLoginWebview extends CommonAuthWebview { public override id: string = 'aws.toolkit.AmazonCommonAuth' @@ -45,6 +46,10 @@ export class ToolkitLoginWebview extends CommonAuthWebview { credentialStartUrl: startUrl, isReAuth: false, } + await globals.globalState.update('recentSso', { + startUrl: startUrl, + region: region, + }) if (this.isCodeCatalystLogin) { return this.ssoSetup('startCodeCatalystSSOSetup', async () => { diff --git a/packages/core/src/shared/extensionGlobals.ts b/packages/core/src/shared/extensionGlobals.ts index e0eca894d7e..6e495339de9 100644 --- a/packages/core/src/shared/extensionGlobals.ts +++ b/packages/core/src/shared/extensionGlobals.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { ExtensionContext, OutputChannel } from 'vscode' +import { ExtensionContext, LogOutputChannel, OutputChannel } from 'vscode' import { LoginManager } from '../auth/deprecated/loginManager' import { AwsResourceManager } from '../dynamicResources/awsResourceManager' import { AWSClientBuilder } from './awsClientBuilder' @@ -191,7 +191,7 @@ export interface ToolkitGlobals { /** * Log messages. Use `outputChannel` for application messages. */ - logOutputChannel: OutputChannel + logOutputChannel: LogOutputChannel loginManager: LoginManager awsContextCommands: AwsContextCommands awsContext: AwsContext diff --git a/packages/core/src/shared/featureConfig.ts b/packages/core/src/shared/featureConfig.ts index 7d653ee6b88..7cc6a9cbfc7 100644 --- a/packages/core/src/shared/featureConfig.ts +++ b/packages/core/src/shared/featureConfig.ts @@ -4,7 +4,6 @@ */ import { - Customization, FeatureValue, ListFeatureEvaluationsRequest, ListFeatureEvaluationsResponse, @@ -20,7 +19,7 @@ import { getClientId, getOperatingSystem } from './telemetry/util' import { extensionVersion } from './vscode/env' import { telemetry } from './telemetry/telemetry' import { Commands } from './vscode/commands2' -import { setSelectedCustomization } from '../codewhisperer/util/customizationUtil' +import { getAvailableCustomizationsList, setSelectedCustomization } from '../codewhisperer/util/customizationUtil' const localize = nls.loadMessageBundle() @@ -152,19 +151,7 @@ export class FeatureConfigProvider { if (AuthUtil.instance.isBuilderIdConnection()) { this.featureConfigs.delete(Features.customizationArnOverride) } else if (AuthUtil.instance.isIdcConnection()) { - let availableCustomizations: Customization[] = [] - try { - const items: Customization[] = [] - const response = await client.listAvailableCustomizations() - for (const customizations of response.map( - (listAvailableCustomizationsResponse) => listAvailableCustomizationsResponse.customizations - )) { - items.push(...customizations) - } - availableCustomizations = items - } catch (e) { - getLogger().debug('amazonq: Failed to list available customizations') - } + const availableCustomizations = await getAvailableCustomizationsList() // If customizationArn from A/B is not available in listAvailableCustomizations response, don't use this value const targetCustomization = availableCustomizations?.find((c) => c.arn === customizationArnOverride) @@ -175,6 +162,16 @@ export class FeatureConfigProvider { this.featureConfigs.delete(Features.customizationArnOverride) } else { await setSelectedCustomization(targetCustomization, true) + // note that we should also switch profile if either + // 1. user has not selected a profile yet + // 2. user's selected profile is not the same as the one of customizationOverride + const profile = AuthUtil.instance.regionProfileManager.activeRegionProfile + if (!profile || (profile && profile.arn !== targetCustomization.profile.arn)) { + await AuthUtil.instance.regionProfileManager.switchRegionProfile( + targetCustomization.profile, + 'customization' + ) + } } await vscode.commands.executeCommand('aws.amazonq.refreshStatusBar') diff --git a/packages/core/src/shared/globalState.ts b/packages/core/src/shared/globalState.ts index 23a67667d88..c38344608b9 100644 --- a/packages/core/src/shared/globalState.ts +++ b/packages/core/src/shared/globalState.ts @@ -50,6 +50,7 @@ export type globalKey = | 'aws.toolkit.lsp.manifest' | 'aws.amazonq.customization.overrideV2' | 'aws.amazonq.regionProfiles' + | 'aws.amazonq.regionProfiles.cache' // Deprecated/legacy names. New keys should start with "aws.". | '#sessionCreationDates' // Legacy name from `ssoAccessTokenProvider.ts`. | 'CODECATALYST_RECONNECT' @@ -69,6 +70,7 @@ export type globalKey = | 'lastSelectedRegion' | 'lastOsStartTime' | 'recentCredentials' + | 'recentSso' // List of regions enabled in AWS Explorer. | 'region' // TODO: implement this via `PromptSettings` instead of globalState. diff --git a/packages/core/src/shared/index.ts b/packages/core/src/shared/index.ts index e2d71396e5e..4cda5285f69 100644 --- a/packages/core/src/shared/index.ts +++ b/packages/core/src/shared/index.ts @@ -27,7 +27,7 @@ export { Prompter } from './ui/prompter' export { VirtualFileSystem } from './virtualFilesystem' export { VirtualMemoryFile } from './virtualMemoryFile' export { AmazonqCreateUpload, Metric } from './telemetry/telemetry' -export { getClientId, getOperatingSystem } from './telemetry/util' +export { getClientId, getOperatingSystem, getOptOutPreference } from './telemetry/util' export { extensionVersion } from './vscode/env' export { cast } from './utilities/typeConstructors' export * as workspaceUtils from './utilities/workspaceUtils' @@ -73,3 +73,4 @@ export * as processUtils from './utilities/processUtils' export * as BaseLspInstaller from './lsp/baseLspInstaller' export * as collectionUtil from './utilities/collectionUtils' export * from './datetime' +export * from './performance/marks' diff --git a/packages/core/src/shared/logger/logger.ts b/packages/core/src/shared/logger/logger.ts index 9b4bead6a37..b398ff93162 100644 --- a/packages/core/src/shared/logger/logger.ts +++ b/packages/core/src/shared/logger/logger.ts @@ -18,6 +18,9 @@ export type LogTopic = | 'chat' | 'stepfunctions' | 'unknown' + | 'nextEditPrediction' + | 'resourceCache' + | 'telemetry' class ErrorLog { constructor( @@ -104,11 +107,6 @@ const logLevels = new Map([ export type LogLevel = 'error' | 'warn' | 'info' | 'verbose' | 'debug' export function fromVscodeLogLevel(logLevel: vscode.LogLevel): LogLevel { - if (!vscode.LogLevel) { - // vscode version <= 1.73 - return 'info' - } - switch (logLevel) { case vscode.LogLevel.Trace: case vscode.LogLevel.Debug: diff --git a/packages/core/src/shared/lsp/baseLspInstaller.ts b/packages/core/src/shared/lsp/baseLspInstaller.ts index ee71b25a4b5..0aeca1dfda4 100644 --- a/packages/core/src/shared/lsp/baseLspInstaller.ts +++ b/packages/core/src/shared/lsp/baseLspInstaller.ts @@ -41,17 +41,23 @@ export abstract class BaseLspInstaller 0) { this.logger.debug(`cleaning old LSP versions: deleted ${deletedVersions.length} versions`) } @@ -71,6 +77,12 @@ export abstract class BaseLspInstaller protected abstract resourcePaths(assetDirectory?: string): T } diff --git a/packages/core/src/shared/lsp/lspResolver.ts b/packages/core/src/shared/lsp/lspResolver.ts index 90892148a9f..e12cac74c40 100644 --- a/packages/core/src/shared/lsp/lspResolver.ts +++ b/packages/core/src/shared/lsp/lspResolver.ts @@ -13,19 +13,31 @@ import { TargetContent, logger, LspResult, LspVersion, Manifest } from './types' import { createHash } from '../crypto' import { lspSetupStage, StageResolver, tryStageResolvers } from './utils/setupStage' import { HttpResourceFetcher } from '../resourcefetcher/httpResourceFetcher' -import { showMessageWithCancel } from '../../shared/utilities/messages' +import { showProgressWithTimeout } from '../../shared/utilities/messages' import { Timeout } from '../utilities/timeoutUtils' +import { oneMinute } from '../datetime' +import vscode from 'vscode' -// max timeout for downloading remote LSP assets progress, the lowest possible is 3000, bounded by httpResourceFetcher's waitUntil -const remoteDownloadTimeout = 5000 +// max timeout for downloading remote LSP assets. Some asserts are large (100+ MB) so this needs to be large for slow connections. +// Since the user can cancel this one we can let it run very long. +const remoteDownloadTimeout = oneMinute * 30 export class LanguageServerResolver { + private readonly downloadMessage: string + constructor( private readonly manifest: Manifest, private readonly lsName: string, private readonly versionRange: semver.Range, + private readonly manifestUrl: string, + /** + * Custom message to show user when downloading, if undefined it will use the default. + */ + downloadMessage?: string, private readonly _defaultDownloadFolder?: string - ) {} + ) { + this.downloadMessage = downloadMessage ?? `Updating '${this.lsName}' language server` + } /** * Downloads and sets up the Language Server, attempting different locations in order: @@ -79,7 +91,22 @@ export class LanguageServerResolver { /** Finds an older, cached version of the LSP server bundle. */ private async getFallbackServer(latestVersion: LspVersion): Promise { - const fallbackDirectory = await this.getFallbackDir(latestVersion.serverVersion) + const cachedVersions = await this.getCachedVersions() + if (cachedVersions.length === 0) { + /** + * at this point the latest version doesn't exist locally, lsp download (with retries) failed, and there are no cached fallback versions. + * This _probably_ only happens when the user hit a firewall/proxy issue, since otherwise they would probably have at least + * one other language server locally + */ + throw new ToolkitError( + `Unable to download dependencies from ${this.manifestUrl}. Check your network connectivity or firewall configuration and then try again.`, + { + code: 'NetworkConnectivityError', + } + ) + } + + const fallbackDirectory = await this.getFallbackDir(latestVersion.serverVersion, cachedVersions) if (!fallbackDirectory) { throw new ToolkitError('Unable to find a compatible version of the Language Server', { code: 'IncompatibleVersion', @@ -104,7 +131,15 @@ export class LanguageServerResolver { */ private async showDownloadProgress() { const timeout = new Timeout(remoteDownloadTimeout) - await showMessageWithCancel(`Downloading '${this.lsName}' language server`, timeout) + void showProgressWithTimeout( + { + title: this.downloadMessage, + location: vscode.ProgressLocation.Notification, + cancellable: false, + }, + timeout, + 0 + ) return timeout } @@ -116,13 +151,14 @@ export class LanguageServerResolver { ): Promise { const timeout = await this.showDownloadProgress() try { - if (await this.downloadRemoteTargetContent(targetContents, latestVersion.serverVersion, timeout)) { + if (await this.downloadRemoteTargetContent(targetContents, latestVersion, timeout)) { return { location: 'remote', version: latestVersion.serverVersion, assetDirectory: cacheDirectory, } } else { + await this.cleanupVersion(latestVersion.serverVersion) throw new ToolkitError('Failed to download server from remote', { code: 'RemoteDownloadFailed' }) } } finally { @@ -130,6 +166,14 @@ export class LanguageServerResolver { } } + private async cleanupVersion(version: string) { + // clean up the X.X.X download directory since the download failed + const downloadDirectory = this.getDownloadDirectory(version) + if (await fs.existsDir(downloadDirectory)) { + await fs.delete(downloadDirectory) + } + } + /** Gets the current local ("cached") LSP server bundle. */ private async getLocalServer( cacheDirectory: string, @@ -159,16 +203,9 @@ export class LanguageServerResolver { /** * Returns the path to the most compatible cached LSP version that can serve as a fallback **/ - private async getFallbackDir(version: string) { + private async getFallbackDir(version: string, cachedVersions: string[]) { const compatibleLspVersions = this.compatibleManifestLspVersion() - // determine all folders containing lsp versions in the fallback parent folder - const cachedVersions = (await fs.readdir(this.defaultDownloadFolder())) - .filter(([_, filetype]) => filetype === FileType.Directory) - .map(([pathName, _]) => semver.parse(pathName)) - .filter((ver): ver is semver.SemVer => ver !== null) - .map((x) => x.version) - const expectedVersion = semver.parse(version) if (!expectedVersion) { return undefined @@ -184,6 +221,15 @@ export class LanguageServerResolver { return fallbackDir.length > 0 ? fallbackDir[0] : undefined } + private async getCachedVersions() { + // determine all folders containing lsp versions in the parent folder + return (await fs.readdir(this.defaultDownloadFolder())) + .filter(([_, filetype]) => filetype === FileType.Directory) + .map(([pathName, _]) => semver.parse(pathName)) + .filter((ver): ver is semver.SemVer => ver !== null) + .map((x) => x.version) + } + /** * Validate the local cache directory of the given lsp version (matches expected hash) * If valid return cache directory, else return undefined @@ -217,8 +263,8 @@ export class LanguageServerResolver { * true, if all of the contents were successfully downloaded and unzipped * false, if any of the contents failed to download or unzip */ - private async downloadRemoteTargetContent(contents: TargetContent[], version: string, timeout: Timeout) { - const downloadDirectory = this.getDownloadDirectory(version) + private async downloadRemoteTargetContent(contents: TargetContent[], lspVersion: LspVersion, timeout: Timeout) { + const downloadDirectory = this.getDownloadDirectory(lspVersion.serverVersion) if (!(await fs.existsDir(downloadDirectory))) { await fs.mkdir(downloadDirectory) @@ -254,6 +300,12 @@ export class LanguageServerResolver { const filesToDownload = await lspSetupStage('validate', async () => (await Promise.all(verifyTasks)).flat()) + // We were instructed by legal to show this message + const thirdPartyLicenses = lspVersion.thirdPartyLicenses + logger.info( + `Installing '${this.lsName}' Language Server v${lspVersion.serverVersion} to: ${downloadDirectory}${thirdPartyLicenses ? ` (Attribution notice can be found at ${thirdPartyLicenses})` : ''}` + ) + for (const file of filesToDownload) { await fs.writeFile(`${downloadDirectory}/${file.filename}`, file.data) } diff --git a/packages/core/src/shared/lsp/manifestResolver.ts b/packages/core/src/shared/lsp/manifestResolver.ts index 60cc466a835..6dd0a793178 100644 --- a/packages/core/src/shared/lsp/manifestResolver.ts +++ b/packages/core/src/shared/lsp/manifestResolver.ts @@ -69,7 +69,6 @@ export class ManifestResolver { const localManifest = await this.getLocalManifest(true).catch(() => undefined) if (localManifest) { - localManifest.location = 'remote' return localManifest } else { // Will emit a `languageServer_setup` result=failed metric... diff --git a/packages/core/src/shared/lsp/types.ts b/packages/core/src/shared/lsp/types.ts index 3cc96dbe681..1262d6e8fd1 100644 --- a/packages/core/src/shared/lsp/types.ts +++ b/packages/core/src/shared/lsp/types.ts @@ -72,6 +72,10 @@ export interface LspVersion { serverVersion: string isDelisted: boolean targets: Target[] + /** + * I'm not sure if this **always** exists (couldn't find it in the spec) + */ + thirdPartyLicenses?: string } export interface Manifest { diff --git a/packages/core/src/shared/lsp/utils/cleanup.ts b/packages/core/src/shared/lsp/utils/cleanup.ts index 83b58e2bb8f..3f8c27cd81d 100644 --- a/packages/core/src/shared/lsp/utils/cleanup.ts +++ b/packages/core/src/shared/lsp/utils/cleanup.ts @@ -23,7 +23,11 @@ function isDelisted(manifestVersions: LspVersion[], targetVersion: string): bool * @param downloadDirectory * @returns array of deleted versions. */ -export async function cleanLspDownloads(manifestVersions: LspVersion[], downloadDirectory: string): Promise { +export async function cleanLspDownloads( + latestInstalledVersion: string, + manifestVersions: LspVersion[], + downloadDirectory: string +): Promise { const downloadedVersions = await getDownloadedVersions(downloadDirectory) const [delistedVersions, remainingVersions] = partition(downloadedVersions, (v: string) => isDelisted(manifestVersions, v) @@ -40,6 +44,15 @@ export async function cleanLspDownloads(manifestVersions: LspVersion[], download } for (const v of sort(remainingVersions).slice(0, -2)) { + /** + * When switching between different manifests, the following edge case can occur: + * A newly downloaded version might chronologically be older than all previously downloaded versions, + * even though it's marked as the latest version in its own manifest. + * In such cases, we skip the cleanup process to preserve this version. Otherwise we will get an EPIPE error + */ + if (v === latestInstalledVersion) { + continue + } await fs.delete(path.join(downloadDirectory, v), { force: true, recursive: true }) deletedVersions.push(v) } diff --git a/packages/core/src/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 5d96ef496f4..2555793ceb5 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -29,19 +29,22 @@ function getEncryptionInit(key: Buffer): string { /** * Checks that we can actually run the `node` executable and execute code with it. */ -export async function validateNodeExe(nodePath: string, lsp: string, args: string[], logger: Logger) { +export async function validateNodeExe(nodePath: string[], lsp: string, args: string[], logger: Logger) { + const bin = nodePath[0] // Check that we can start `node` by itself. - const proc = new ChildProcess(nodePath, ['-e', 'console.log("ok " + process.version)'], { logging: 'no' }) + const proc = new ChildProcess(bin, [...nodePath.slice(1), '-e', 'console.log("ok " + process.version)'], { + logging: 'no', + }) const r = await proc.run() const ok = r.exitCode === 0 && r.stdout.includes('ok') if (!ok) { const msg = `failed to run basic "node -e" test (exitcode=${r.exitCode}): ${proc.toString(false, true)}` logger.error(msg) - throw new ToolkitError(`amazonqLsp: ${msg}`) + throw new ToolkitError(`amazonqLsp: ${msg}`, { code: 'FailedToRunNode' }) } // Check that we can start `node …/lsp.js --stdio …`. - const lspProc = new ChildProcess(nodePath, [lsp, ...args], { logging: 'no' }) + const lspProc = new ChildProcess(bin, [...nodePath.slice(1), lsp, ...args], { logging: 'no' }) try { // Start asynchronously (it never stops; we need to stop it below). lspProc.run().catch((e) => logger.error('failed to run: %s', lspProc.toString(false, true))) @@ -65,7 +68,8 @@ export async function validateNodeExe(nodePath: string, lsp: string, args: strin }) if (!ok2 || selfExit) { throw new ToolkitError( - `amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}` + `amazonqLsp: failed to run (exitcode=${lspProc.exitCode()}): ${lspProc.toString(false, true)}`, + { code: 'FailedToStartLanguageServer' } ) } } finally { @@ -78,18 +82,21 @@ export function createServerOptions({ executable, serverModule, execArgv, + warnThresholds, }: { encryptionKey: Buffer - executable: string + executable: string[] serverModule: string execArgv: string[] + warnThresholds?: { cpu?: number; memory?: number } }) { return async () => { - const args = [serverModule, ...execArgv] + const bin = executable[0] + const args = [...executable.slice(1), serverModule, ...execArgv] if (isDebugInstance()) { args.unshift('--inspect=6080') } - const lspProcess = new ChildProcess(executable, args) + const lspProcess = new ChildProcess(bin, args, { warnThresholds }) // this is a long running process, awaiting it will never resolve void lspProcess.run() diff --git a/packages/core/src/shared/settings-amazonq.gen.ts b/packages/core/src/shared/settings-amazonq.gen.ts index f0a3d47f989..637c5b1b12e 100644 --- a/packages/core/src/shared/settings-amazonq.gen.ts +++ b/packages/core/src/shared/settings-amazonq.gen.ts @@ -21,7 +21,9 @@ export const amazonqSettings = { "ssoCacheError": {}, "amazonQLspManifestMessage": {}, "amazonQWorkspaceLspManifestMessage": {}, - "amazonQChatDisclaimer": {} + "amazonQChatDisclaimer": {}, + "amazonQChatPairProgramming": {}, + "amazonQSelectDeveloperProfile": {} }, "amazonQ.showCodeWithReferences": {}, "amazonQ.allowFeatureDevelopmentToRunCodeAndTests": {}, @@ -31,6 +33,9 @@ export const amazonqSettings = { "amazonQ.workspaceIndexWorkerThreads": {}, "amazonQ.workspaceIndexUseGPU": {}, "amazonQ.workspaceIndexMaxSize": {}, + "amazonQ.workspaceIndexMaxFileSize": {}, + "amazonQ.workspaceIndexCacheDirPath": {}, + "amazonQ.workspaceIndexIgnoreFilePatterns": {}, "amazonQ.ignoredSecurityIssues": {} } diff --git a/packages/core/src/shared/settings-toolkit.gen.ts b/packages/core/src/shared/settings-toolkit.gen.ts index 95aaa173c2e..59a637a4870 100644 --- a/packages/core/src/shared/settings-toolkit.gen.ts +++ b/packages/core/src/shared/settings-toolkit.gen.ts @@ -42,6 +42,7 @@ export const toolkitSettings = { }, "aws.experiments": { "jsonResourceModification": {}, + "amazonqLSP": {}, "amazonqLSPInline": {}, "amazonqChatLSP": {} }, diff --git a/packages/core/src/shared/settings.ts b/packages/core/src/shared/settings.ts index 71a2f83a77c..4e3e99f8207 100644 --- a/packages/core/src/shared/settings.ts +++ b/packages/core/src/shared/settings.ts @@ -389,6 +389,10 @@ function createSettingsClass(section: string, descript public _getOrThrow(key: K & string, defaultValue?: Inner[K]) { const value = this.#config.get(key, defaultValue) + if (defaultValue !== undefined && (value === undefined || value === null)) { + return defaultValue + } + return cast(value, descriptor[key]) } diff --git a/packages/core/src/shared/telemetry/util.ts b/packages/core/src/shared/telemetry/util.ts index 34dd7c9eeac..4d136bc96f0 100644 --- a/packages/core/src/shared/telemetry/util.ts +++ b/packages/core/src/shared/telemetry/util.ts @@ -8,7 +8,7 @@ import { env, version } from 'vscode' import * as os from 'os' import { getLogger } from '../logger/logger' import { fromExtensionManifest, Settings } from '../settings' -import { memoize, once } from '../utilities/functionUtils' +import { memoize, once, oncePerUniqueArg } from '../utilities/functionUtils' import { isInDevEnv, extensionVersion, @@ -19,7 +19,7 @@ import { } from '../vscode/env' import { addTypeName } from '../utilities/typeConstructors' import globals, { isWeb } from '../extensionGlobals' -import { mapMetadata } from './telemetryLogger' +import { mapMetadata, MetadataObj } from './telemetryLogger' import { Result } from './telemetry.gen' import { MetricDatum } from './clienttelemetry' import { isValidationExemptMetric } from './exemptMetrics' @@ -312,35 +312,51 @@ export async function getComputeEnvType(): Promise { /** * Validates that emitted telemetry metrics - * 1. contain a result property and + * 1. contain a result property * 2. contain a reason propery if result = 'Failed'. + * 3. are not missing fields */ -export function validateMetricEvent(event: MetricDatum, fatal: boolean) { +export function validateMetricEvent(event: MetricDatum, fatal: boolean, isExempt = isValidationExemptMetric) { + if (!isExempt(event.MetricName) && event.Metadata) { + const metadata = mapMetadata([])(event.Metadata) + validateMetadata(event.MetricName, metadata, fatal) + } +} + +function validateMetadata(metricName: string, metadata: MetadataObj, fatal: boolean) { const failedStr: Result = 'Failed' - const telemetryRunDocsStr = + const preferRunSuffix = ' Consider using `.run()` instead of `.emit()`, which will set these properties automatically. ' + 'See https://github.com/aws/aws-toolkit-vscode/blob/master/docs/telemetry.md#guidelines' - - if (!isValidationExemptMetric(event.MetricName) && event.Metadata) { - const metadata = mapMetadata([])(event.Metadata) - let msg = 'telemetry: invalid Metric: ' - - if (metadata.result === undefined) { - msg += `"${event.MetricName}" emitted without the \`result\` property, which is always required.` - } else if (metadata.result === failedStr && metadata.reason === undefined) { - msg += `"${event.MetricName}" emitted with result=Failed but without the \`reason\` property.` - } else { - return // Validation passed. - } - - msg += telemetryRunDocsStr + const msgPrefix = 'invalid Metric: ' + const logger = getTelemetryLogger() + const logOrThrow = (msg: string, includeSuffix: boolean) => { + const fullMsg = msgPrefix + msg + (includeSuffix ? preferRunSuffix : '') + logger.warn(fullMsg) if (fatal) { - throw new Error(msg) + throw new Error('telemetry: ' + fullMsg) } - getLogger().warn(msg) + } + + if (metadata.result === undefined) { + logOrThrow(`"${metricName}" emitted without the \`result\` property, which is always required.`, true) + } else if (metadata.result === failedStr && metadata.reason === undefined) { + logOrThrow(`"${metricName}" emitted with result=Failed but without the \`reason\` property.`, true) + } + + // TODO: there are many instances in the toolkit where we emit metrics with missing fields. If those can be removed, we can configure this to throw in CI. + if (metadata.missingFields) { + const logMsg = `${msgPrefix} "${metricName}" emitted with missing fields: ${metadata.missingFields}` + logWarningOnce(logMsg) } } +function getTelemetryLogger() { + return getLogger('telemetry') +} + +const logWarningOnce = oncePerUniqueArg((m: string) => getTelemetryLogger().warn(m)) + /** * Potentially helpful values for the 'source' field in telemetry. */ diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index ffd715cbed8..b28aeec4847 100644 --- a/packages/core/src/shared/telemetry/vscodeTelemetry.json +++ b/packages/core/src/shared/telemetry/vscodeTelemetry.json @@ -87,11 +87,6 @@ "type": "int", "description": "CPU used by LSP server as a percentage of all available CPUs on the system" }, - { - "name": "cwsprChatResponseErrorReason", - "type": "string", - "description": "Client error reason when processing response stream" - }, { "name": "cwsprChatInteractionTarget", "type": "string", @@ -665,56 +660,6 @@ } ] }, - { - "name": "amazonq_messageResponseError", - "description": "When an error has occured in response to a prompt", - "metadata": [ - { - "type": "cwsprChatConversationId", - "required": false - }, - { - "type": "credentialStartUrl", - "required": false - }, - { - "type": "cwsprChatTriggerInteraction" - }, - { - "type": "cwsprChatUserIntent", - "required": false - }, - { - "type": "cwsprChatHasCodeSnippet", - "required": false - }, - { - "type": "cwsprChatProgrammingLanguage", - "required": false - }, - { - "type": "cwsprChatActiveEditorTotalCharacters", - "required": false - }, - { - "type": "cwsprChatActiveEditorImportCount", - "required": false - }, - { - "type": "cwsprChatResponseCode" - }, - { - "type": "cwsprChatRequestLength" - }, - { - "type": "cwsprChatConversationType" - }, - { - "type": "cwsprChatResponseErrorReason", - "required": false - } - ] - }, { "name": "amazonq_modifyCode", "description": "% of code modified by the user after copying/inserting code from a message", @@ -1074,6 +1019,15 @@ } ] }, + { + "name": "languageServer_crash", + "description": "Called when a language server crash is detected. TODO: Port this to common", + "metadata": [ + { + "type": "id" + } + ] + }, { "name": "ide_heartbeat", "description": "A heartbeat sent by the extension", diff --git a/packages/core/src/shared/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index 9f9fe9875b9..a5a5e83e7d6 100644 --- a/packages/core/src/shared/utilities/collectionUtils.ts +++ b/packages/core/src/shared/utilities/collectionUtils.ts @@ -7,6 +7,7 @@ import { isWeb } from '../extensionGlobals' import { inspect as nodeInspect } from 'util' import { AsyncCollection, toCollection } from './asyncCollection' import { SharedProp, AccumulableKeys, Coalesce, isNonNullable } from './tsUtils' +import { truncate } from './textUtilities' export function union(a: Iterable, b: Iterable): Set { const result = new Set() @@ -304,10 +305,22 @@ export function assign, U extends Partial>(data: T * @param depth * @param omitKeys Omit properties matching these names (at any depth). * @param replacement Replacement for object whose fields extend beyond `depth`, and properties matching `omitKeys`. + * @param maxStringLength truncates string values that exceed this threshold (includes values in nested arrays) */ -export function partialClone(obj: any, depth: number = 3, omitKeys: string[] = [], replacement?: any): any { +export function partialClone( + obj: any, + depth: number = 3, + omitKeys: string[] = [], + options?: { + replacement?: any + maxStringLength?: number + } +): any { // Base case: If input is not an object or has no children, return it. if (typeof obj !== 'object' || obj === null || 0 === Object.getOwnPropertyNames(obj).length) { + if (typeof obj === 'string' && options?.maxStringLength) { + return truncate(obj, options?.maxStringLength, '...') + } return obj } @@ -315,15 +328,15 @@ export function partialClone(obj: any, depth: number = 3, omitKeys: string[] = [ const clonedObj = Array.isArray(obj) ? [] : {} if (depth === 0) { - return replacement ? replacement : clonedObj + return options?.replacement ? options.replacement : clonedObj } // Recursively clone properties of the input object for (const key in obj) { if (omitKeys.includes(key)) { - ;(clonedObj as any)[key] = replacement ? replacement : Array.isArray(obj) ? [] : {} + ;(clonedObj as any)[key] = options?.replacement ? options.replacement : Array.isArray(obj) ? [] : {} } else if (Object.prototype.hasOwnProperty.call(obj, key)) { - ;(clonedObj as any)[key] = partialClone(obj[key], depth - 1, omitKeys, replacement) + ;(clonedObj as any)[key] = partialClone(obj[key], depth - 1, omitKeys, options) } } @@ -567,25 +580,25 @@ export function isPresent(value: T | undefined): value is T { return value !== undefined } -export class CircularBuffer { - private buffer = new Set() +export class CircularBuffer { + private buffer = new Set() private maxSize: number constructor(size: number) { this.maxSize = size } - add(value: number): void { + add(value: T): void { if (this.buffer.size >= this.maxSize) { // Set iterates its keys in insertion-order. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set const firstKey = this.buffer.keys().next().value - this.buffer.delete(firstKey) + this.buffer.delete(firstKey as T) } this.buffer.add(value) } - contains(value: number): boolean { + contains(value: T): boolean { return this.buffer.has(value) } diff --git a/packages/core/src/shared/utilities/functionUtils.ts b/packages/core/src/shared/utilities/functionUtils.ts index 3bcb87730fa..cbf89340ade 100644 --- a/packages/core/src/shared/utilities/functionUtils.ts +++ b/packages/core/src/shared/utilities/functionUtils.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { CircularBuffer } from './collectionUtils' import { Timeout } from './timeoutUtils' /** @@ -156,3 +157,43 @@ export function keyedDebounce( return promise } } +/** + * Creates a function that runs only for unique arguments that haven't been seen before. + * + * This utility tracks all unique inputs it has seen and only executes the callback + * for new inputs. Unlike `onceChanged` which only compares with the previous invocation, + * this function maintains a history of all arguments it has processed. + * + * @param fn The function to execute for unique arguments + * @param key A function that returns a unique string for each argument set, defaults to joining with ":" seperating + * @param overflow The maximum number of unique arguments to store. + * @returns A wrapped function that only executes for new unique arguments + * + * @example + * ```ts + * const logOnce = oncePerUniqueArg((message) => console.log(message)) + * + * logOnce('hello') // prints: hello + * logOnce('world') // prints: world + * logOnce('hello') // nothing happens (already seen) + * logOnce('test') // prints: test + * ``` + */ +export function oncePerUniqueArg( + fn: (...args: U) => T, + options?: { key?: (...args: U) => string; overflow?: number } +): (...args: U) => T | undefined { + const seen = new CircularBuffer(options?.overflow ?? 1000) + const keyMap = options?.key ?? ((...args: U) => args.map(String).join(':')) + + return (...args) => { + const signature = keyMap(...args) + + if (!seen.contains(signature)) { + seen.add(signature) + return fn(...args) + } + + return undefined + } +} diff --git a/packages/core/src/shared/utilities/messages.ts b/packages/core/src/shared/utilities/messages.ts index fe2b08e42cf..26fd745c8d6 100644 --- a/packages/core/src/shared/utilities/messages.ts +++ b/packages/core/src/shared/utilities/messages.ts @@ -236,7 +236,7 @@ export function showOutputMessage(message: string, outputChannel: vscode.OutputC * * @see showMessageWithCancel for an example usage */ -async function showProgressWithTimeout( +export async function showProgressWithTimeout( options: vscode.ProgressOptions, timeout: Timeout, showAfterMs: number diff --git a/packages/core/src/shared/utilities/processUtils.ts b/packages/core/src/shared/utilities/processUtils.ts index 0204736f500..c24b445cab4 100644 --- a/packages/core/src/shared/utilities/processUtils.ts +++ b/packages/core/src/shared/utilities/processUtils.ts @@ -8,7 +8,7 @@ import * as crossSpawn from 'cross-spawn' import * as logger from '../logger/logger' import { Timeout, CancellationError, waitUntil } from './timeoutUtils' import { PollingSet } from './pollingSet' -import { CircularBuffer } from './collectionUtils' +import { oncePerUniqueArg } from './functionUtils' export interface RunParameterContext { /** Reports an error parsed from the stdin/stdout streams. */ @@ -44,6 +44,13 @@ export interface ChildProcessOptions { onStdout?: (text: string, context: RunParameterContext) => void /** Callback for intercepting text from the stderr stream. */ onStderr?: (text: string, context: RunParameterContext) => void + /** Thresholds to configure warning logs */ + warnThresholds?: { + /** Threshold for memory usage in bytes */ + memory?: number + /** Threshold for CPU usage by percentage */ + cpu?: number + } } export interface ChildProcessRunOptions extends Omit { @@ -60,8 +67,12 @@ export interface ChildProcessResult { stderr: string signal?: string } - +export const oneMB = 1024 * 1024 export const eof = Symbol('EOF') +export const defaultProcessWarnThresholds = { + memory: 100 * oneMB, + cpu: 50, +} export interface ProcessStats { memory: number @@ -69,12 +80,7 @@ export interface ProcessStats { } export class ChildProcessTracker { static readonly pollingInterval: number = 10000 // Check usage every 10 seconds - static readonly thresholds: ProcessStats = { - memory: 100 * 1024 * 1024, // 100 MB - cpu: 50, - } static readonly logger = logger.getLogger('childProcess') - static readonly loggedPids = new CircularBuffer(1000) #processByPid: Map = new Map() #pids: PollingSet @@ -82,6 +88,15 @@ export class ChildProcessTracker { this.#pids = new PollingSet(ChildProcessTracker.pollingInterval, () => this.monitor()) } + private getThreshold(pid: number): ProcessStats { + if (!this.#processByPid.has(pid)) { + ChildProcessTracker.logOnce(pid, `Missing process with id ${pid}, returning default threshold`) + return defaultProcessWarnThresholds + } + // Safe to assert since it exists from check above. + return this.#processByPid.get(pid)!.getWarnThresholds() + } + private cleanUp() { const terminatedProcesses = Array.from(this.#pids.values()).filter( (pid: number) => this.#processByPid.get(pid)?.stopped @@ -106,23 +121,27 @@ export class ChildProcessTracker { return } const stats = this.getUsage(pid) + const threshold = this.getThreshold(pid) if (stats) { ChildProcessTracker.logger.debug(`Process ${pid} usage: %O`, stats) - if (stats.memory > ChildProcessTracker.thresholds.memory) { - ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded memory threshold: ${stats.memory}`) + if (stats.memory > threshold.memory) { + ChildProcessTracker.logOnce( + pid, + `Process ${pid} exceeded memory threshold: ${(stats.memory / oneMB).toFixed(2)} MB` + ) } - if (stats.cpu > ChildProcessTracker.thresholds.cpu) { - ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded cpu threshold: ${stats.cpu}`) + if (stats.cpu > threshold.cpu) { + ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded cpu threshold: ${stats.cpu}%`) } } } - public static logOnce(pid: number, msg: string) { - if (!ChildProcessTracker.loggedPids.contains(pid)) { - ChildProcessTracker.loggedPids.add(pid) + public static logOnce = oncePerUniqueArg( + (_pid: number, msg: string) => { ChildProcessTracker.logger.warn(msg) - } - } + }, + { key: (pid, _) => `${pid}` } + ) public add(childProcess: ChildProcess) { const pid = childProcess.pid() @@ -248,6 +267,10 @@ export class ChildProcess { return await new ChildProcess(command, args, options).run() } + public getWarnThresholds(): ProcessStats { + return { ...defaultProcessWarnThresholds, ...this.#baseOptions.warnThresholds } + } + // Inspired by 'got' /** * Creates a one-off {@link ChildProcess} class that always uses the specified options. diff --git a/packages/core/src/shared/utilities/resourceCache.ts b/packages/core/src/shared/utilities/resourceCache.ts new file mode 100644 index 00000000000..c0beee61cd6 --- /dev/null +++ b/packages/core/src/shared/utilities/resourceCache.ts @@ -0,0 +1,190 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import globals from '../extensionGlobals' +import { globalKey } from '../globalState' +import { getLogger } from '../logger/logger' +import { waitUntil } from '../utilities/timeoutUtils' + +/** + * args: + * @member result: the actual resource type callers want to use + * @member locked: readWriteLock, while the lock is acquired by one process, the other can't access to it until it's released by the previous + * @member timestamp: used for determining the resource is stale or not + */ +interface Resource { + result: V | undefined + locked: boolean + timestamp: number +} + +/** + * GlobalStates schema, which is used for vscode global states deserialization, [globals.globalState#tryGet] etc. + * The purpose of it is to allow devs to overload the resource into existing global key and no need to create a specific key for only this purpose. + */ +export interface GlobalStateSchema { + resource: Resource +} + +const logger = getLogger('resourceCache') + +function now() { + return globals.clock.Date.now() +} + +/** + * CacheResource utilizes VSCode global states API to cache resources which are expensive to get so that the result can be shared across multiple VSCode instances. + * The first VSCode instance invoking #getResource will hold a lock and make the actual network call/FS read to pull the real response. + * When the pull is done, the lock will be released and it then caches the result in the global states. Then the rest of instances can now acquire the lock 1 by 1 and read the resource from the cache. + * + * constructor: + * @param key: global state key, which is used for globals.globalState#update, #tryGet etc. + * @param expirationInMilli: cache expiration time in milli seconds + * @param defaultValue: default value for the cache if the cache doesn't pre-exist in users' FS + * @param waitUntilOption: waitUntil option for acquire lock + * + * methods: + * @method resourceProvider: implementation needs to implement this method to obtain the latest resource either via network calls or FS read + * @method getResource: obtain the resource from cache or pull the latest from the service if the cache either expires or doesn't exist + */ +export abstract class CachedResource { + constructor( + private readonly key: globalKey, + private readonly expirationInMilli: number, + private readonly defaultValue: GlobalStateSchema, + private readonly waitUntilOption: { timeout: number; interval: number; truthy: boolean } + ) {} + + abstract resourceProvider(): Promise + + async getResource(): Promise { + const cachedValue = await this.tryLoadResourceAndLock() + const resource = cachedValue?.resource + + // If cache is still fresh, return cached result, otherwise pull latest from the service + if (cachedValue && resource && resource.result) { + const duration = now() - resource.timestamp + if (duration < this.expirationInMilli) { + logger.debug( + `cache hit, duration(%sms) is less than expiration(%sms), returning cached value: %s`, + duration, + this.expirationInMilli, + this.key + ) + // release the lock + await this.releaseLock(resource, cachedValue) + return resource.result + } + + logger.debug( + `cache is stale, duration(%sms) is older than expiration(%sms), pulling latest resource: %s`, + duration, + this.expirationInMilli, + this.key + ) + } else { + logger.info(`cache miss, pulling latest resource: %s`, this.key) + } + + /** + * Possible paths here + * 1. cache doesn't exist. + * 2. cache exists but expired. + * 3. lock is held by other process and the waiting time is greater than the specified waiting time + */ + try { + // Make the real network call / FS read to pull the resource + const latest = await this.resourceProvider() + + // Update resource cache and release the lock + const r: Resource = { + locked: false, + timestamp: now(), + result: latest, + } + await this.releaseLock(r) + logger.info(`loaded latest resource and updated cache: %s`, this.key) + return latest + } catch (e) { + logger.error(`failed to load latest resource, releasing lock: %s`, this.key) + await this.releaseLock() + throw e + } + } + + // This method will lock the resource so other callers have to wait until the lock is released, otherwise will return undefined if it times out + private async tryLoadResourceAndLock(): Promise | undefined> { + const _acquireLock = async () => { + const cachedValue = this.readCacheOrDefault() + + if (!cachedValue.resource.locked) { + await this.lockResource(cachedValue) + return cachedValue + } + + return undefined + } + + const lock = await waitUntil(async () => { + const lock = await _acquireLock() + logger.debug(`trying to acquire resource cache lock: %s`, this.key) + if (lock) { + return lock + } + }, this.waitUntilOption) + + return lock + } + + async lockResource(baseCache: GlobalStateSchema): Promise { + await this.updateResourceCache({ locked: true }, baseCache) + } + + async releaseLock(): Promise + async releaseLock(resource: Partial>): Promise + async releaseLock(resource: Partial>, baseCache: GlobalStateSchema): Promise + async releaseLock(resource?: Partial>, baseCache?: GlobalStateSchema): Promise { + if (!resource) { + await this.updateResourceCache({ locked: false }, undefined) + } else if (baseCache) { + await this.updateResourceCache(resource, baseCache) + } else { + await this.updateResourceCache(resource, undefined) + } + } + + async clearCache() { + const baseCache = this.readCacheOrDefault() + await this.updateResourceCache({ result: undefined, timestamp: 0, locked: false }, baseCache) + } + + private async updateResourceCache(resource: Partial>, cache: GlobalStateSchema | undefined) { + const baseCache = cache ?? this.readCacheOrDefault() + + const toUpdate: GlobalStateSchema = { + ...baseCache, + resource: { + ...baseCache.resource, + ...resource, + }, + } + + await globals.globalState.update(this.key, toUpdate) + } + + private readCacheOrDefault(): GlobalStateSchema { + const cachedValue = globals.globalState.tryGet>(this.key, Object, { + ...this.defaultValue, + resource: { + ...this.defaultValue.resource, + locked: false, + result: undefined, + timestamp: 0, + }, + }) + + return cachedValue + } +} diff --git a/packages/core/src/shared/utilities/textDocumentUtilities.ts b/packages/core/src/shared/utilities/textDocumentUtilities.ts index 48a20a6c44b..93b22c2e560 100644 --- a/packages/core/src/shared/utilities/textDocumentUtilities.ts +++ b/packages/core/src/shared/utilities/textDocumentUtilities.ts @@ -11,6 +11,8 @@ import { getLogger } from '../logger/logger' import fs from '../fs/fs' import { ToolkitError } from '../errors' import { indent } from './textUtilities' +import { ViewDiffMessage } from '../../amazonq/commons/controllers/contentController' +import { DiffContentProvider } from '../../amazonq/commons/controllers/diffContentProvider' /** * Finds occurences of text in a document. Currently only used for highlighting cloudwatchlogs data. @@ -123,13 +125,13 @@ export async function applyChanges(doc: vscode.TextDocument, range: vscode.Range * and applying the proposed changes within the selected range. * * @param {vscode.Uri} originalFileUri - The URI of the original file. - * @param {any} message - The message object containing the proposed code changes. + * @param {ViewDiffMessage} message - The message object containing the proposed code changes. * @param {vscode.Selection} selection - The selection range in the document where the changes are applied. * @returns {Promise} - A promise that resolves to the URI of the temporary file. */ export async function createTempFileForDiff( originalFileUri: vscode.Uri, - message: any, + message: ViewDiffMessage, selection: vscode.Selection, scheme: string ): Promise { @@ -168,6 +170,53 @@ export async function createTempFileForDiff( return tempFileUri } +/** + * Creates temporary URIs for diff comparison and registers their content with a DiffContentProvider. + * This approach avoids writing to the file system by keeping content in memory. + * + * @param filePath The path of the original file (used for naming) + * @param fileText Optional content of the original file (if not provided, will be read from filePath) + * @param message The message object containing the proposed code changes + * @param selection The selection range where changes should be applied + * @param scheme The URI scheme to use + * @param diffProvider The content provider to register URIs with + * @returns A promise that resolves to a tuple of [originalUri, modifiedUri] + */ +export async function createTempUrisForDiff( + filePath: string, + fileText: string | undefined, + message: ViewDiffMessage, + selection: vscode.Selection, + scheme: string, + diffProvider: DiffContentProvider +): Promise<[vscode.Uri, vscode.Uri]> { + const originalFile = _path.parse(filePath) + const id = Date.now() + + // Create URIs with the custom scheme + const originalFileUri = vscode.Uri.parse(`${scheme}:/${originalFile.name}_original-${id}${originalFile.ext}`) + const modifiedFileUri = vscode.Uri.parse(`${scheme}:/${originalFile.name}_proposed-${id}${originalFile.ext}`) + + // Get the original content + const contentToUse = fileText ?? (await fs.readFileText(filePath)) + + // Register the original content + diffProvider.registerContent(originalFileUri, contentToUse) + + const indentedCode = getIndentedCodeFromOriginalContent(message, contentToUse, selection) + const lines = contentToUse.split('\n') + + // Create the modified content + const beforeLines = lines.slice(0, selection.start.line) + const afterLines = lines.slice(selection.end.line + 1) + const modifiedContent = [...beforeLines, indentedCode, ...afterLines].join('\n') + + // Register the modified content + diffProvider.registerContent(modifiedFileUri, modifiedContent) + + return [originalFileUri, modifiedFileUri] +} + /** * Indents the given code based on the current document's indentation at the selection start. * @@ -176,7 +225,7 @@ export async function createTempFileForDiff( * @param selection The selection range in the document. * @returns The processed code to be applied to the document. */ -export function getIndentedCode(message: any, doc: vscode.TextDocument, selection: vscode.Selection) { +export function getIndentedCode(message: ViewDiffMessage, doc: vscode.TextDocument, selection: vscode.Selection) { const indentRange = new vscode.Range(new vscode.Position(selection.start.line, 0), selection.active) let indentation = doc.getText(indentRange) @@ -187,6 +236,26 @@ export function getIndentedCode(message: any, doc: vscode.TextDocument, selectio return indent(message.code, indentation.length) } +/** + * Indents the given code based on the indentation of the original content at the selection start. + * + * @param message The message object containing the code. + * @param originalContent The original content of the document. + * @param selection The selection range in the document. + * @returns The processed code to be applied to the document. + */ +export function getIndentedCodeFromOriginalContent( + message: ViewDiffMessage, + originalContent: string, + selection: vscode.Selection +) { + const lines = originalContent.split('\n') + const selectionStartLine = lines[selection.start.line] || '' + const indentMatch = selectionStartLine.match(/^(\s*)/) + const indentation = indentMatch ? indentMatch[1] : '' + return indent(message.code, indentation.length) +} + export async function showFile(uri: vscode.Uri) { const doc = await vscode.workspace.openTextDocument(uri) await vscode.window.showTextDocument(doc, { preview: false }) diff --git a/packages/core/src/shared/utilities/textUtilities.ts b/packages/core/src/shared/utilities/textUtilities.ts index 53c2e2be32c..6bc1a4eff21 100644 --- a/packages/core/src/shared/utilities/textUtilities.ts +++ b/packages/core/src/shared/utilities/textUtilities.ts @@ -8,9 +8,10 @@ import * as crypto from 'crypto' import * as fs from 'fs' // eslint-disable-line no-restricted-imports import { default as stripAnsi } from 'strip-ansi' import { getLogger } from '../logger/logger' +import { ViewDiffMessage } from '../../amazonq/commons/controllers/contentController' /** - * Truncates string `s` if it exceeds `n` chars. + * Truncates string `s` if it has or exceeds `n` chars. * * If `n` is negative, truncates at start instead of end. * @@ -268,10 +269,11 @@ export function decodeBase64(base64Str: string): string { * @param {any} message - The message object containing the file and selection context. * @returns {Object} - An object with `filePath` and `selection` properties. */ -export function extractFileAndCodeSelectionFromMessage(message: any) { +export function extractFileAndCodeSelectionFromMessage(message: ViewDiffMessage) { const filePath = message?.context?.activeFileContext?.filePath + const fileText = message?.context?.activeFileContext?.fileText const selection = message?.context?.focusAreaContext?.selectionInsideExtendedCodeBlock as vscode.Selection - return { filePath, selection } + return { filePath, fileText, selection } } export function matchesPattern(source: string, target: string | RegExp) { diff --git a/packages/core/src/shared/vscode/commands2.ts b/packages/core/src/shared/vscode/commands2.ts index c55cd66cc7a..b40134c2afa 100644 --- a/packages/core/src/shared/vscode/commands2.ts +++ b/packages/core/src/shared/vscode/commands2.ts @@ -653,7 +653,7 @@ async function runCommand(fn: T, info: CommandInfo): Prom logger.debug( `command: running ${label} with arguments: %O`, - partialClone(args, 3, ['clientSecret', 'accessToken', 'refreshToken', 'tooltip'], '[omitted]') + partialClone(args, 3, ['clientSecret', 'accessToken', 'refreshToken', 'tooltip'], { replacement: '[omitted]' }) ) try { diff --git a/packages/core/src/test/amazonq/common/contentController.test.ts b/packages/core/src/test/amazonq/common/contentController.test.ts index f0fb069844d..aac3b852e96 100644 --- a/packages/core/src/test/amazonq/common/contentController.test.ts +++ b/packages/core/src/test/amazonq/common/contentController.test.ts @@ -6,12 +6,16 @@ import * as vscode from 'vscode' import assert from 'assert' import { EditorContentController } from '../../../amazonq/commons/controllers/contentController' import { toTextEditor } from '../../testUtil' +import { CWCTelemetryHelper } from '../../../codewhispererChat/controllers/chat/telemetryHelper' +import { ChatSessionStorage } from '../../../codewhispererChat/storages/chatSession' +import { TriggerEventsStorage } from '../../../codewhispererChat' describe('contentController', () => { let controller: EditorContentController beforeEach(async () => { controller = new EditorContentController() + CWCTelemetryHelper.instance = new CWCTelemetryHelper(new ChatSessionStorage(), new TriggerEventsStorage()) }) describe('insertTextAtCursorPosition', () => { @@ -31,7 +35,7 @@ describe('contentController', () => { } }) - it('insert code when left hand size has non empty character', async () => { + it('insert code when left hand size has non empty character 2', async () => { const editor = await toTextEditor('def hello_world():\n ', 'test.py') if (editor) { const pos = new vscode.Position(0, 4) diff --git a/packages/core/src/test/amazonq/commons/controllers/contentController.test.ts b/packages/core/src/test/amazonq/commons/controllers/contentController.test.ts new file mode 100644 index 00000000000..003e14e4783 --- /dev/null +++ b/packages/core/src/test/amazonq/commons/controllers/contentController.test.ts @@ -0,0 +1,143 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import * as sinon from 'sinon' +import assert from 'assert' +import { EditorContentController, ViewDiffMessage } from '../../../../amazonq/commons/controllers/contentController' +import * as textDocumentUtilities from '../../../../shared/utilities/textDocumentUtilities' +import * as textUtilities from '../../../../shared/utilities/textUtilities' +import { amazonQDiffScheme, amazonQTabSuffix } from '../../../../shared/constants' +import * as editorUtilities from '../../../../shared/utilities/editorUtilities' + +describe('EditorContentController', () => { + let sandbox: sinon.SinonSandbox + let controller: EditorContentController + let executeCommandStub: sinon.SinonStub + let registerTextDocumentContentProviderStub: sinon.SinonStub + let createTempUrisForDiffStub: sinon.SinonStub + let disposeOnEditorCloseStub: sinon.SinonStub + let extractParamsFromMessageStub: sinon.SinonStub + + beforeEach(() => { + sandbox = sinon.createSandbox() + controller = new EditorContentController() + + // Stub VS Code API calls + executeCommandStub = sandbox.stub(vscode.commands, 'executeCommand').resolves() + registerTextDocumentContentProviderStub = sandbox + .stub(vscode.workspace, 'registerTextDocumentContentProvider') + .returns({ dispose: () => {} }) + + // Stub utility functions + createTempUrisForDiffStub = sandbox.stub(textDocumentUtilities, 'createTempUrisForDiff') + disposeOnEditorCloseStub = sandbox.stub(editorUtilities, 'disposeOnEditorClose') + extractParamsFromMessageStub = sandbox.stub(textUtilities, 'extractFileAndCodeSelectionFromMessage') + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('viewDiff', () => { + const testFilePath = '/path/to/testFile.js' + const testCode = 'new code' + const testSelection = new vscode.Selection(new vscode.Position(1, 0), new vscode.Position(2, 0)) + const testMessage: ViewDiffMessage = { + code: testCode, + } + const originalUri = vscode.Uri.parse('test-scheme:/original/testFile.js') + const modifiedUri = vscode.Uri.parse('test-scheme:/modified/testFile.js') + + beforeEach(() => { + extractParamsFromMessageStub.returns({ + filePath: testFilePath, + selection: testSelection, + }) + createTempUrisForDiffStub.resolves([originalUri, modifiedUri]) + }) + + it('should show diff view with correct URIs and title', async () => { + await controller.viewDiff(testMessage) + + // Verify content provider was registered + assert.strictEqual(registerTextDocumentContentProviderStub.calledOnce, true) + assert.strictEqual(registerTextDocumentContentProviderStub.firstCall.args[0], amazonQDiffScheme) + + // Verify createTempUrisForDiff was called with correct parameters + assert.strictEqual(createTempUrisForDiffStub.calledOnce, true) + assert.strictEqual(createTempUrisForDiffStub.firstCall.args[0], testFilePath) + assert.strictEqual(createTempUrisForDiffStub.firstCall.args[2], testMessage) + assert.strictEqual(createTempUrisForDiffStub.firstCall.args[3], testSelection) + assert.strictEqual(createTempUrisForDiffStub.firstCall.args[4], amazonQDiffScheme) + + // Verify vscode.diff command was executed with correct parameters + assert.strictEqual(executeCommandStub.calledOnce, true) + assert.strictEqual(executeCommandStub.firstCall.args[0], 'vscode.diff') + assert.strictEqual(executeCommandStub.firstCall.args[1], originalUri) + assert.strictEqual(executeCommandStub.firstCall.args[2], modifiedUri) + assert.strictEqual(executeCommandStub.firstCall.args[3], `testFile.js ${amazonQTabSuffix}`) + + // Verify disposeOnEditorClose was called + assert.strictEqual(disposeOnEditorCloseStub.calledOnce, true) + assert.strictEqual(disposeOnEditorCloseStub.firstCall.args[0], originalUri) + }) + + it('should use custom scheme when provided', async () => { + const customScheme = 'custom-scheme' + await controller.viewDiff(testMessage, customScheme) + + assert.strictEqual(registerTextDocumentContentProviderStub.firstCall.args[0], customScheme) + assert.strictEqual(createTempUrisForDiffStub.firstCall.args[4], customScheme) + }) + + it('should pass fileText to createTempUrisForDiff when available', async () => { + const testFileText = 'original file content' + extractParamsFromMessageStub.returns({ + filePath: testFilePath, + fileText: testFileText, + selection: testSelection, + }) + + await controller.viewDiff(testMessage) + + assert.strictEqual(createTempUrisForDiffStub.firstCall.args[1], testFileText) + }) + + it('should not attempt to show diff when filePath is missing', async () => { + extractParamsFromMessageStub.returns({ + filePath: undefined, + selection: testSelection, + }) + + await controller.viewDiff(testMessage) + + assert.strictEqual(registerTextDocumentContentProviderStub.called, false) + assert.strictEqual(createTempUrisForDiffStub.called, false) + assert.strictEqual(executeCommandStub.called, false) + }) + + it('should not attempt to show diff when code is missing', async () => { + await controller.viewDiff({ code: undefined as unknown as string }) + + assert.strictEqual(registerTextDocumentContentProviderStub.called, false) + assert.strictEqual(createTempUrisForDiffStub.called, false) + assert.strictEqual(executeCommandStub.called, false) + }) + + it('should not attempt to show diff when selection is missing', async () => { + extractParamsFromMessageStub.returns({ + filePath: testFilePath, + selection: undefined, + }) + + await controller.viewDiff(testMessage) + + assert.strictEqual(registerTextDocumentContentProviderStub.called, false) + assert.strictEqual(createTempUrisForDiffStub.called, false) + assert.strictEqual(executeCommandStub.called, false) + }) + }) +}) diff --git a/packages/core/src/test/amazonq/customizationUtil.test.ts b/packages/core/src/test/amazonq/customizationUtil.test.ts index bb852afba2e..19e59b91c03 100644 --- a/packages/core/src/test/amazonq/customizationUtil.test.ts +++ b/packages/core/src/test/amazonq/customizationUtil.test.ts @@ -11,18 +11,54 @@ import { AuthUtil, baseCustomization, Customization, + CustomizationProvider, FeatureConfigProvider, getSelectedCustomization, refreshStatusBar, + RegionProfileManager, setSelectedCustomization, } from '../../codewhisperer' import { FeatureContext, globals } from '../../shared' import { resetCodeWhispererGlobalVariables } from '../codewhisperer/testUtil' import { createSsoProfile, createTestAuth } from '../credentials/testUtil' -import { LanguageClientAuth } from '../../auth/auth2' +import { createTestAuthUtil } from '../testAuthUtil' const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' +describe('customizationProvider', function () { + let regionProfileManager: RegionProfileManager + + beforeEach(async () => { + createTestAuth(globals.globalState) + await createTestAuthUtil() + regionProfileManager = new RegionProfileManager(AuthUtil.instance) + }) + + afterEach(() => { + sinon.restore() + }) + + it('init should create new instance with client', async function () { + const mockAuthUtil = { + regionProfileManager: regionProfileManager, + } + sinon.stub(AuthUtil, 'instance').get(() => mockAuthUtil) + const createClientStub = sinon.stub(regionProfileManager, 'createQClient') + const mockProfile = { + name: 'foo', + region: 'us-east-1', + arn: 'arn', + description: '', + } + + const provider = await CustomizationProvider.init(mockProfile) + assert(provider instanceof CustomizationProvider) + assert(createClientStub.calledOnce) + assert(createClientStub.calledWith(mockProfile)) + assert.strictEqual(provider.region, 'us-east-1') + }) +}) + describe('CodeWhisperer-customizationUtils', function () { let auth: ReturnType let featureCustomization: FeatureContext @@ -30,13 +66,11 @@ describe('CodeWhisperer-customizationUtils', function () { before(async function () { createTestAuth(globals.globalState) tryRegister(refreshStatusBar) - const mockLspAuth: Partial = { - registerSsoTokenChangedHandler: sinon.stub().resolves(), - } - AuthUtil.create(mockLspAuth as LanguageClientAuth) }) beforeEach(async function () { + await createTestAuthUtil() + auth = createTestAuth(globals.globalState) await auth.createInvalidSsoConnection( createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) diff --git a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts index 3ec69e70b04..4b478e1876e 100644 --- a/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts +++ b/packages/core/src/test/codewhisperer/commands/transformByQ.test.ts @@ -41,6 +41,9 @@ import { setMaven, parseBuildFile, validateSQLMetadataFile, + createLocalBuildUploadZip, + validateCustomVersionsFile, + extractOriginalProjectSources, } from '../../../codewhisperer/service/transformByQ/transformFileHandler' import { uploadArtifactToS3 } from '../../../codewhisperer/indexNode' import request from '../../../shared/request' @@ -49,6 +52,19 @@ import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports describe('transformByQ', function () { let fetchStub: sinon.SinonStub let tempDir: string + const validCustomVersionsFile = `name: "custom-dependency-management" +description: "Custom dependency version management for Java migration from JDK 8/11/17 to JDK 17/21" +dependencyManagement: + dependencies: + - identifier: "com.example:library1" + targetVersion: "2.1.0" + versionProperty: "library1.version" + originType: "FIRST_PARTY" + plugins: + - identifier: "com.example.plugin" + targetVersion: "1.2.0" + versionProperty: "plugin.version"` + const validSctFile = ` @@ -284,8 +300,55 @@ describe('transformByQ', function () { const tempFilePath = path.join(tempDir, tempFileName) await toFile('', tempFilePath) transformByQState.setProjectPath(tempDir) - await setMaven() - assert.strictEqual(transformByQState.getMavenName(), '.\\mvnw.cmd') + setMaven() + // mavenName should always be 'mvn' + assert.strictEqual(transformByQState.getMavenName(), 'mvn') + }) + + it(`WHEN local build zip created THEN zip contains all expected files and no unexpected files`, async function () { + const zipPath = await createLocalBuildUploadZip(tempDir, 0, 'sample stdout after running local build') + const zip = new AdmZip(zipPath) + const manifestEntry = zip.getEntry('manifest.json') + if (!manifestEntry) { + fail('manifest.json not found in the zip') + } + const manifestBuffer = manifestEntry.getData() + const manifestText = manifestBuffer.toString('utf8') + const manifest = JSON.parse(manifestText) + assert.strictEqual(manifest.capability, 'CLIENT_SIDE_BUILD') + assert.strictEqual(manifest.exitCode, 0) + assert.strictEqual(manifest.commandLogFileName, 'build-output.log') + assert.strictEqual(zip.getEntries().length, 2) // expecting only manifest.json and build-output.log + }) + + it('WHEN extractOriginalProjectSources THEN only source files are extracted to destination', async function () { + const tempDir = (await TestFolder.create()).path + const destinationPath = path.join(tempDir, 'originalCopy_jobId_artifactId') + await fs.mkdir(destinationPath) + + const zip = new AdmZip() + const testFiles = [ + { path: 'sources/file1.java', content: 'test content 1' }, + { path: 'sources/dir/file2.java', content: 'test content 2' }, + { path: 'dependencies/file3.jar', content: 'should not extract' }, + { path: 'manifest.json', content: '{"version": "1.0"}' }, + ] + + for (const file of testFiles) { + zip.addFile(file.path, Buffer.from(file.content)) + } + + const zipPath = path.join(tempDir, 'test.zip') + zip.writeZip(zipPath) + + transformByQState.setPayloadFilePath(zipPath) + + await extractOriginalProjectSources(destinationPath) + + const extractedFiles = getFilesRecursively(destinationPath, false) + assert.strictEqual(extractedFiles.length, 2) + assert(extractedFiles.includes(path.join(destinationPath, 'sources', 'file1.java'))) + assert(extractedFiles.includes(path.join(destinationPath, 'sources', 'dir', 'file2.java'))) }) it(`WHEN zip created THEN manifest.json contains test-compile custom build command`, async function () { @@ -453,6 +516,17 @@ describe('transformByQ', function () { assert.strictEqual(expectedWarning, warningMessage) }) + it(`WHEN validateCustomVersionsFile on fully valid .yaml file THEN passes validation`, async function () { + const isValidFile = await validateCustomVersionsFile(validCustomVersionsFile) + assert.strictEqual(isValidFile, true) + }) + + it(`WHEN validateCustomVersionsFile on invalid .yaml file THEN fails validation`, async function () { + const invalidFile = validCustomVersionsFile.replace('dependencyManagement', 'invalidKey') + const isValidFile = await validateCustomVersionsFile(invalidFile) + assert.strictEqual(isValidFile, false) + }) + it(`WHEN validateMetadataFile on fully valid .sct file THEN passes validation`, async function () { const isValidMetadata = await validateSQLMetadataFile(validSctFile, { tabID: 'abc123' }) assert.strictEqual(isValidMetadata, true) diff --git a/packages/core/src/test/codewhisperer/nextEditPrediction/predictionTracker.test.ts b/packages/core/src/test/codewhisperer/nextEditPrediction/predictionTracker.test.ts new file mode 100644 index 00000000000..715044fdf73 --- /dev/null +++ b/packages/core/src/test/codewhisperer/nextEditPrediction/predictionTracker.test.ts @@ -0,0 +1,344 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import * as sinon from 'sinon' +import assert from 'assert' +import * as path from 'path' + +import { + FileSnapshot, + FileTrackerConfig, + PredictionTracker, +} from '../../../codewhisperer/nextEditPrediction/predictionTracker' +import { FakeExtensionContext } from '../../fakeExtensionContext' +import { createMockDocument } from '../testUtil' +import * as diffGenerator from '../../../codewhisperer/nextEditPrediction/diffContextGenerator' +import globals from '../../../shared/extensionGlobals' +import { charactersLimit, supplementalContextMaxTotalLength } from '../../../codewhisperer/models/constants' + +describe('PredictionTracker', function () { + let sandbox: sinon.SinonSandbox + let mockExtensionContext: vscode.ExtensionContext + let tracker: PredictionTracker + let clock: sinon.SinonFakeTimers + let dateNowStub: sinon.SinonStub + + beforeEach(async function () { + sandbox = sinon.createSandbox() + // Set a base time for tests + const startTime = new Date('2025-04-21T12:00:00Z').getTime() + + clock = sandbox.useFakeTimers({ + now: startTime, + shouldAdvanceTime: true, + }) + + // Set up a stub for globals.clock.Date.now() that we can control manually + dateNowStub = sandbox.stub(globals.clock.Date, 'now') + dateNowStub.returns(startTime) + + mockExtensionContext = await FakeExtensionContext.create() + }) + + afterEach(function () { + sandbox.restore() + clock.restore() + }) + + describe('processEdit', function () { + let filePath: string + let previousContent: string + let mockDocument: vscode.TextDocument + + beforeEach(function () { + filePath = testPath('path', 'to', 'file.js') + previousContent = 'previous content' + tracker = new PredictionTracker(mockExtensionContext) + + // Create a mock document + mockDocument = createMockDocument(previousContent, filePath) + }) + + it('should store snapshot in memory', async function () { + await tracker.processEdit(mockDocument, previousContent) + const snapshots = tracker.getFileSnapshots(filePath) + + assert.strictEqual(snapshots.length, 1) + assert.strictEqual(snapshots[0].content, previousContent) + assert.strictEqual(snapshots[0].size, Buffer.byteLength(previousContent, 'utf8')) + }) + + it('should not add new snapshot within debounce interval', async function () { + await tracker.processEdit(mockDocument, 'first edit') + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 1) + + // Another edit within debounce interval, should not add another snapshot + await tracker.processEdit(mockDocument, 'second edit') + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 1) + }) + + it('should add new snapshot after debounce interval', async function () { + const initialTime = globals.clock.Date.now() + await tracker.processEdit(mockDocument, 'first edit') + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 1) + + // Another edit after debounce interval, should add another snapshot + const laterTime = initialTime + tracker.config.debounceIntervalMs + 1000 + dateNowStub.returns(laterTime) + await tracker.processEdit(mockDocument, 'second edit') + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 2) + + // Verify the content of the second snapshot + const snapshots = tracker.getFileSnapshots(filePath) + assert.strictEqual(snapshots[1].content, 'second edit') + }) + + it('should delete snapshot after maxAgeMs', async function () { + const customConfig: Partial = { + maxAgeMs: 10000, + } + tracker = new PredictionTracker(mockExtensionContext, customConfig) + const initialTime = globals.clock.Date.now() + await tracker.processEdit(mockDocument, previousContent) + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 1) + + // Advance time just under the maxAgeMs, snapshot should still exist + dateNowStub.returns(initialTime + tracker.config.maxAgeMs - 1000) + await clock.tickAsync(tracker.config.maxAgeMs - 1000) + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 1) + + // Advance time past the maxAgeMs, snapshot should be removed + dateNowStub.returns(initialTime + tracker.config.maxAgeMs + 2000) + await clock.tickAsync(3000) + assert.strictEqual(tracker.getFileSnapshots(filePath).length, 0) + }) + }) + + describe('enforceMemoryLimits', function () { + beforeEach(function () { + tracker = new PredictionTracker(mockExtensionContext) + }) + + it('should remove oldest snapshots when storage size exceeds limit', async function () { + // Very small storage limit + const customConfig: Partial = { + maxStorageSizeKb: 0.1, + } + tracker = new PredictionTracker(mockExtensionContext, customConfig) + + const file1 = testPath('path', 'to', 'file1.js') + const file2 = testPath('path', 'to', 'file2.js') + + const initialTime = globals.clock.Date.now() + + // First snapshot for file1 (oldest) + const mockDocument1 = createMockDocument('content 1', file1) + await tracker.processEdit(mockDocument1, 'content 1') + dateNowStub.returns(initialTime + 1000) + await clock.tickAsync(1000) + + // Second snapshot for file1 + await tracker.processEdit(mockDocument1, 'content 2') + dateNowStub.returns(initialTime + 2000) + await clock.tickAsync(1000) + + // First snapshot for file2 + const mockDocument2 = createMockDocument('content 3', file2) + await tracker.processEdit(mockDocument2, 'content 3') + + await (tracker as any).enforceMemoryLimits() + + // Oldest snapshot should be removed + const file1Snapshots = tracker.getFileSnapshots(file1) + assert.strictEqual(file1Snapshots.length, 1) + }) + }) + + describe('getFileSnapshots', function () { + beforeEach(function () { + tracker = new PredictionTracker(mockExtensionContext) + }) + + it('should return empty array for non-existent file', function () { + const result = tracker.getFileSnapshots(testPath('non-existent', 'file.js')) + assert.deepStrictEqual(result, []) + }) + + it('should return snapshots for existing file', async function () { + const file = testPath('path', 'to', 'file.js') + const content = 'file content' + const mockDocument = createMockDocument(content, file) + await tracker.processEdit(mockDocument, content) + + const result = tracker.getFileSnapshots(file) + assert.strictEqual(result.length, 1) + assert.strictEqual(result[0].filePath, file) + assert.strictEqual(result[0].content, content) + }) + }) + + describe('getSnapshotContent', function () { + let file: string + let snapshotContent: string + let snapshot: FileSnapshot + + beforeEach(async function () { + tracker = new PredictionTracker(mockExtensionContext) + file = testPath('path', 'to', 'file.js') + snapshotContent = 'snapshot content' + const mockDocument = createMockDocument(snapshotContent, file) + await tracker.processEdit(mockDocument, snapshotContent) + + snapshot = tracker.getFileSnapshots(file)[0] + }) + + it('should retrieve snapshot content from memory', async function () { + const content = await tracker.getSnapshotContent(snapshot) + assert.strictEqual(content, snapshotContent) + }) + }) + + describe('generatePredictionSupplementalContext', function () { + let mockEditor: vscode.TextEditor + let diffGenerateStub: sinon.SinonStub + + beforeEach(function () { + tracker = new PredictionTracker(mockExtensionContext) + + // Mock active editor, we only care about document + mockEditor = { + document: createMockDocument('current content', testPath('path', 'to', 'active.js')), + selection: new vscode.Selection(0, 0, 0, 0), + selections: [new vscode.Selection(0, 0, 0, 0)], + options: {}, + visibleRanges: [], + edit: () => Promise.resolve(true), + insertSnippet: () => Promise.resolve(true), + setDecorations: () => {}, + revealRange: () => {}, + show: () => {}, + hide: () => {}, + viewColumn: vscode.ViewColumn.One, + } as vscode.TextEditor + + sandbox.stub(vscode.window, 'activeTextEditor').value(mockEditor) + + // Mock diffGenerator.generateDiffContexts + diffGenerateStub = sandbox.stub(diffGenerator, 'generateDiffContexts').resolves([]) + }) + + it('should return empty array if no snapshots', async function () { + const result = await tracker.generatePredictionSupplementalContext() + assert.deepStrictEqual(result, []) + }) + + it('should generate and return supplemental contexts', async function () { + const filePath = testPath('path', 'to', 'active.js') + const initialTime = globals.clock.Date.now() + + const mockDoc = createMockDocument('old content 1', filePath) + await tracker.processEdit(mockDoc, 'old content 1') + dateNowStub.returns(initialTime + tracker.config.debounceIntervalMs + 1000) + await clock.tickAsync(tracker.config.debounceIntervalMs + 1000) + await tracker.processEdit(mockDoc, 'old content 2') + + const mockContexts = [ + { filePath, content: 'diff1', type: 'PreviousEditorState' }, + { filePath, content: 'diff2', type: 'PreviousEditorState' }, + ] + diffGenerateStub.resolves(mockContexts) + + const result = await tracker.generatePredictionSupplementalContext() + + // Should have called generateDiffContexts with the right params + assert.ok(diffGenerateStub.called) + assert.strictEqual(diffGenerateStub.args[0][0], filePath) + assert.strictEqual(diffGenerateStub.args[0][1], 'current content') + assert.strictEqual(diffGenerateStub.args[0][2].length, 2) + assert.strictEqual(diffGenerateStub.args[0][3], tracker.config.maxSupplementalContext) + + // Should return the contexts from generateDiffContexts + assert.deepStrictEqual(result, mockContexts) + + // Check that the snapshot content is correctly passed to the diffContextGenerator + const snapshotContents = diffGenerateStub.args[0][2] + assert.strictEqual(snapshotContents[0].content, 'old content 1') + assert.strictEqual(snapshotContents[1].content, 'old content 2') + }) + }) + + function testPath(...segments: string[]): string { + // Mock the path from vscode uri + return path.sep + path.join(...segments) + } + + describe('trimSupplementalContexts', function () { + it('should filter out contexts that exceed individual character limit', function () { + const smallContext = { + filePath: 'file.js', + content: 'small content', + type: 'PreviousEditorState', + } + + // Create a context that exceeds the characters limit + const largeContent = 'a'.repeat(charactersLimit + 100) + const largeContext = { + filePath: 'file.js', + content: largeContent, + type: 'PreviousEditorState', + } + + const contexts = [smallContext, largeContext] + const result = diffGenerator.trimSupplementalContexts(contexts, 10) + + assert.strictEqual(result.length, 1) + assert.deepStrictEqual(result[0], smallContext) + }) + + it('should limit the number of contexts to maxContexts', function () { + const contexts = [ + { filePath: 'file1.js', content: 'content 1', type: 'PreviousEditorState' }, + { filePath: 'file2.js', content: 'content 2', type: 'PreviousEditorState' }, + { filePath: 'file3.js', content: 'content 3', type: 'PreviousEditorState' }, + { filePath: 'file4.js', content: 'content 4', type: 'PreviousEditorState' }, + { filePath: 'file5.js', content: 'content 5', type: 'PreviousEditorState' }, + ] + + const maxContexts = 3 + const result = diffGenerator.trimSupplementalContexts(contexts, maxContexts) + + assert.strictEqual(result.length, maxContexts) + }) + + it('should enforce total character length limit across all contexts', function () { + // Create contexts where total size exceeds the limit + const contentSize = Math.floor(supplementalContextMaxTotalLength / 2.5) + const contexts = [ + { + filePath: 'file1.js', + content: 'a'.repeat(contentSize), + type: 'PreviousEditorState', + }, + { + filePath: 'file2.js', + content: 'b'.repeat(contentSize), + type: 'PreviousEditorState', + }, + { + filePath: 'file3.js', + content: 'c'.repeat(contentSize), + type: 'PreviousEditorState', + }, + ] + + const result = diffGenerator.trimSupplementalContexts(contexts, 10) + + // Only the first two contexts should be included since the third would exceed the total limit + assert.strictEqual(result.length, 2) + assert.deepStrictEqual(result, contexts.slice(0, 2)) + }) + }) +}) diff --git a/packages/core/src/test/codewhisperer/startSecurityScan.test.ts b/packages/core/src/test/codewhisperer/startSecurityScan.test.ts index 18a2235d3ee..38b00a2bdd3 100644 --- a/packages/core/src/test/codewhisperer/startSecurityScan.test.ts +++ b/packages/core/src/test/codewhisperer/startSecurityScan.test.ts @@ -28,9 +28,9 @@ import { import * as model from '../../codewhisperer/models/model' import * as errors from '../../shared/errors' import * as timeoutUtils from '../../shared/utilities/timeoutUtils' -import { AuthUtil, SecurityIssueTreeViewProvider } from '../../codewhisperer' +import { SecurityIssueTreeViewProvider } from '../../codewhisperer' import { createClient, mockGetCodeScanResponse } from './testUtil' -import { LanguageClientAuth } from '../../auth/auth2' +import { createTestAuthUtil } from '../testAuthUtil' let extensionContext: FakeExtensionContext let mockSecurityPanelViewProvider: SecurityPanelViewProvider @@ -42,14 +42,8 @@ let focusStub: sinon.SinonStub describe('startSecurityScan', function () { const workspaceFolder = getTestWorkspaceFolder() - before(async function () { - const mockLspAuth: Partial = { - registerSsoTokenChangedHandler: sinon.stub().resolves(), - } - AuthUtil.create(mockLspAuth as LanguageClientAuth) - }) - beforeEach(async function () { + await createTestAuthUtil() extensionContext = await FakeExtensionContext.create() mockSecurityPanelViewProvider = new SecurityPanelViewProvider(extensionContext) appRoot = join(workspaceFolder, 'python3.7-plain-sam-app') diff --git a/packages/core/src/test/codewhispererChat/editor/codelens.test.ts b/packages/core/src/test/codewhispererChat/editor/codelens.test.ts index 8ae1d715004..3e9bd9284a5 100644 --- a/packages/core/src/test/codewhispererChat/editor/codelens.test.ts +++ b/packages/core/src/test/codewhispererChat/editor/codelens.test.ts @@ -24,7 +24,8 @@ import { PressTabState, TryMoreExState, } from '../../../codewhisperer/views/lineAnnotationController' -import { AuthState, LanguageClientAuth } from '../../../auth/auth2' +import { AuthState } from '../../../auth/auth2' +import { createTestAuthUtil } from '../../testAuthUtil' describe('TryChatCodeLensProvider', () => { let instance: TryChatCodeLensProvider @@ -41,13 +42,10 @@ describe('TryChatCodeLensProvider', () => { // that originally would have been registered by the `core` `activate()` at some point tryRegister(tryChatCodeLensCommand) tryRegister(focusAmazonQPanel) - const mockLspAuth: Partial = { - registerSsoTokenChangedHandler: sinon.stub().resolves(), - } - AuthUtil.create(mockLspAuth as LanguageClientAuth) }) beforeEach(async function () { + await createTestAuthUtil() isAmazonQVisibleEventEmitter = new vscode.EventEmitter() isAmazonQVisibleEvent = isAmazonQVisibleEventEmitter.event instance = new TryChatCodeLensProvider(isAmazonQVisibleEvent, () => codeLensPosition) diff --git a/packages/core/src/test/index.ts b/packages/core/src/test/index.ts index 9a01973e26d..0a4203f2fe5 100644 --- a/packages/core/src/test/index.ts +++ b/packages/core/src/test/index.ts @@ -25,3 +25,4 @@ export * from './testUtil' export * from './amazonq/utils' export * from './fake/mockFeatureConfigData' export * from './shared/ui/testUtils' +export * from './testAuthUtil' diff --git a/packages/core/src/test/login/webview/vue/backend_amazonq.test.ts b/packages/core/src/test/login/webview/vue/backend_amazonq.test.ts deleted file mode 100644 index 75e4f163461..00000000000 --- a/packages/core/src/test/login/webview/vue/backend_amazonq.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// // import { assertTelemetry } from '../../../testUtil' -// import assert from 'assert' -// import { AmazonQLoginWebview } from '../../../../login/webview/vue/amazonq/backend_amazonq' -// import { AuthUtil } from '../../../../codewhisperer/util/authUtil' -// import * as sinon from 'sinon' -// import { LanguageClientAuth } from '../../../../auth/auth2' - -// describe('Amazon Q Login', function () { -// const region = 'fakeRegion' -// const startUrl = 'fakeUrl' - -// let sandbox: sinon.SinonSandbox -// let backend: AmazonQLoginWebview - -// const mockLspAuth: Partial = { -// registerSsoTokenChangedHandler: sinon.stub().resolves(), -// }; -// AuthUtil.create(mockLspAuth as LanguageClientAuth); - -// beforeEach(function () { -// sandbox = sinon.createSandbox() -// backend = new AmazonQLoginWebview() -// }) - -// afterEach(function () { -// sandbox.restore() -// }) - -// it('signs into builder ID and emits telemetry', async function () { -// await backend.startBuilderIdSetup() - -// assert.ok(AuthUtil.instance.isConnected()) -// assert.ok(AuthUtil.instance.isBuilderIdConnection()) - -// // TODO: @opieter implement telemetry -// // assertTelemetry('auth_addConnection', { -// // result: 'Succeeded', -// // credentialSourceId: 'awsId', -// // authEnabledFeatures: 'codewhisperer', -// // isReAuth: false, -// // ssoRegistrationExpiresAt: mockRegistration.expiresAt.toISOString(), -// // ssoRegistrationClientId: mockRegistration.clientId, -// // }) -// }) - -// it('signs into IdC and emits telemetry', async function () { -// await backend.startEnterpriseSetup(startUrl, region) - -// assert.ok(AuthUtil.instance.isConnected()) -// assert.ok(AuthUtil.instance.isIdcConnection()) -// assert.ok(AuthUtil.instance.isSsoSession()) -// assert.deepStrictEqual(AuthUtil.instance.connection?.startUrl, startUrl) -// assert.deepStrictEqual(AuthUtil.instance.connection?.region, region) - -// // TODO: @opieter implement telemetry -// // assertTelemetry('auth_addConnection', { -// // result: 'Succeeded', -// // credentialSourceId: 'iamIdentityCenter', -// // authEnabledFeatures: 'codewhisperer', -// // credentialStartUrl: startUrl, -// // awsRegion: region, -// // isReAuth: false, -// // ssoRegistrationExpiresAt: mockRegistration.expiresAt.toISOString(), -// // ssoRegistrationClientId: mockRegistration.clientId, -// // }) -// }) - -// it('reauths builder ID and emits telemetry', async function () { -// AuthUtil.instance.logout() - -// // method under test -// await backend.reauthenticateConnection() - -// assert.ok(AuthUtil.instance.isConnected()) - -// // TODO: @opieter implement telemetry -// // assertTelemetry('auth_addConnection', { -// // result: 'Succeeded', -// // credentialSourceId: 'awsId', -// // authEnabledFeatures: 'codewhisperer', -// // isReAuth: true, -// // ssoRegistrationExpiresAt: mockRegistration.expiresAt.toISOString(), -// // ssoRegistrationClientId: mockRegistration.clientId, -// // }) -// }) - -// it('reauths IdC and emits telemetry', async function () { -// AuthUtil.instance.logout() - -// // method under test -// await backend.reauthenticateConnection() - -// assert.ok(AuthUtil.instance.isConnected()) - -// // TODO: @opieter implement telemetry -// // assertTelemetry('auth_addConnection', { -// // result: 'Succeeded', -// // credentialSourceId: 'iamIdentityCenter', -// // authEnabledFeatures: 'codewhisperer', -// // credentialStartUrl: startUrl, -// // awsRegion: region, -// // isReAuth: true, -// // ssoRegistrationExpiresAt: mockRegistration.expiresAt.toISOString(), -// // ssoRegistrationClientId: mockRegistration.clientId, -// // }) -// }) - -// it('signs out of reauth and emits telemetry', async function () { -// await backend.signout() - -// assert.ok(!AuthUtil.instance.isConnected()) - -// // TODO: @opieter implement telemetry -// // assertTelemetry('auth_addConnection', { -// // result: 'Cancelled', -// // credentialSourceId: 'iamIdentityCenter', -// // authEnabledFeatures: 'codewhisperer', -// // credentialStartUrl: startUrl, -// // awsRegion: region, -// // isReAuth: true, -// // ssoRegistrationExpiresAt: mockRegistration.expiresAt.toISOString(), -// // ssoRegistrationClientId: mockRegistration.clientId, -// // }) -// }) -// }) diff --git a/packages/core/src/test/shared/featureConfig.test.ts b/packages/core/src/test/shared/featureConfig.test.ts index dd84f59d554..e94358361b0 100644 --- a/packages/core/src/test/shared/featureConfig.test.ts +++ b/packages/core/src/test/shared/featureConfig.test.ts @@ -7,18 +7,15 @@ import assert from 'assert' import sinon from 'sinon' import { AWSError, Request } from 'aws-sdk' import { Features, FeatureConfigProvider, featureDefinitions, FeatureName } from '../../shared/featureConfig' -import { AuthUtil, ListFeatureEvaluationsResponse } from '../../codewhisperer' +import { ListFeatureEvaluationsResponse } from '../../codewhisperer' import { createSpyClient } from '../codewhisperer/testUtil' import { mockFeatureConfigsData } from '../fake/mockFeatureConfigData' -import { LanguageClientAuth } from '../../auth/auth2' +import { createTestAuthUtil } from '../testAuthUtil' describe('FeatureConfigProvider', () => { - const mockLspAuth: Partial = { - registerSsoTokenChangedHandler: sinon.stub().resolves(), - } - AuthUtil.create(mockLspAuth as LanguageClientAuth) - beforeEach(async () => { + await createTestAuthUtil() + const clientSpy = await createSpyClient() sinon.stub(clientSpy, 'listFeatureEvaluations').returns({ promise: () => diff --git a/packages/core/src/test/shared/lsp/utils/cleanup.test.ts b/packages/core/src/test/shared/lsp/utils/cleanup.test.ts index 98f37fff28f..ec2f5a32059 100644 --- a/packages/core/src/test/shared/lsp/utils/cleanup.test.ts +++ b/packages/core/src/test/shared/lsp/utils/cleanup.test.ts @@ -41,7 +41,7 @@ describe('cleanLSPDownloads', function () { it('keeps two newest versions', async function () { await fakeInstallVersions(['1.0.0', '1.0.1', '1.1.1', '2.1.1'], installationDir.fsPath) - const deleted = await cleanLspDownloads([], installationDir.fsPath) + const deleted = await cleanLspDownloads('2.1.1', [], installationDir.fsPath) const result = (await fs.readdir(installationDir.fsPath)).map(([filename, _filetype], _index) => filename) assert.strictEqual(result.length, 2) @@ -53,6 +53,7 @@ describe('cleanLSPDownloads', function () { it('deletes delisted versions', async function () { await fakeInstallVersions(['1.0.0', '1.0.1', '1.1.1', '2.1.1'], installationDir.fsPath) const deleted = await cleanLspDownloads( + '2.1.1', [{ serverVersion: '1.1.1', isDelisted: true, targets: [] }], installationDir.fsPath ) @@ -67,6 +68,7 @@ describe('cleanLSPDownloads', function () { it('handles case where less than 2 versions are not delisted', async function () { await fakeInstallVersions(['1.0.0', '1.0.1', '1.1.1', '2.1.1'], installationDir.fsPath) const deleted = await cleanLspDownloads( + '1.0.1', [ { serverVersion: '1.1.1', isDelisted: true, targets: [] }, { serverVersion: '2.1.1', isDelisted: true, targets: [] }, @@ -83,7 +85,7 @@ describe('cleanLSPDownloads', function () { it('handles case where less than 2 versions exist', async function () { await fakeInstallVersions(['1.0.0'], installationDir.fsPath) - const deleted = await cleanLspDownloads([], installationDir.fsPath) + const deleted = await cleanLspDownloads('1.0.0', [], installationDir.fsPath) const result = (await fs.readdir(installationDir.fsPath)).map(([filename, _filetype], _index) => filename) assert.strictEqual(result.length, 1) @@ -93,6 +95,7 @@ describe('cleanLSPDownloads', function () { it('does not install delisted version when no other option exists', async function () { await fakeInstallVersions(['1.0.0'], installationDir.fsPath) const deleted = await cleanLspDownloads( + '1.0.0', [{ serverVersion: '1.0.0', isDelisted: true, targets: [] }], installationDir.fsPath ) @@ -105,6 +108,7 @@ describe('cleanLSPDownloads', function () { it('ignores invalid versions', async function () { await fakeInstallVersions(['1.0.0', '.DS_STORE'], installationDir.fsPath) const deleted = await cleanLspDownloads( + '1.0.0', [{ serverVersion: '1.0.0', isDelisted: true, targets: [] }], installationDir.fsPath ) diff --git a/packages/core/src/test/shared/telemetry/util.test.ts b/packages/core/src/test/shared/telemetry/util.test.ts index 27cfcb6b4f7..8d6f3ddc53f 100644 --- a/packages/core/src/test/shared/telemetry/util.test.ts +++ b/packages/core/src/test/shared/telemetry/util.test.ts @@ -15,12 +15,15 @@ import { SessionId, telemetryClientIdEnvKey, TelemetryConfig, + validateMetricEvent, } from '../../../shared/telemetry/util' import { extensionVersion } from '../../../shared/vscode/env' import { FakeMemento } from '../../fakeExtensionContext' import { GlobalState } from '../../../shared/globalState' import { randomUUID } from 'crypto' import { isUuid } from '../../../shared/crypto' +import { MetricDatum } from '../../../shared/telemetry/clienttelemetry' +import { assertLogsContain } from '../../globalSetup.test' describe('TelemetryConfig', function () { const settingKey = 'aws.telemetry' @@ -281,3 +284,110 @@ describe('getUserAgent', function () { assert.strictEqual(beforeClient, platformPair()) }) }) + +describe('validateMetricEvent', function () { + it('does not validate exempt metrics', function () { + const metricEvent: MetricDatum = { + MetricName: 'exempt_metric', + Value: 1, + Unit: 'None', + Metadata: [{ Key: 'result', Value: 'Succeeded' }], + } as MetricDatum + + validateMetricEvent(metricEvent, true, (_) => true) + assert.throws(() => assertLogsContain('invalid Metric', false, 'warn')) + }) + + it('passes validation for metrics with proper result property', function () { + const metricEvent: MetricDatum = { + MetricName: 'valid_metric', + Value: 1, + Unit: 'None', + Metadata: [{ Key: 'result', Value: 'Succeeded' }], + } as MetricDatum + + validateMetricEvent(metricEvent, true, (_) => false) + assert.throws(() => assertLogsContain('invalid Metric', false, 'warn')) + }) + + it('passes validation for metrics with Failed result and reason property', function () { + const metricEvent: MetricDatum = { + MetricName: 'valid_failed_metric', + Value: 1, + Unit: 'None', + Metadata: [ + { Key: 'result', Value: 'Failed' }, + { Key: 'reason', Value: 'Something went wrong' }, + ], + } as MetricDatum + + validateMetricEvent(metricEvent, true, (_) => false) + }) + + it('fails validation for metrics missing result property when fatal=true', function () { + const metricEvent: MetricDatum = { + MetricName: 'invalid_metric_no_result', + Value: 1, + Unit: 'None', + Metadata: [{ Key: 'someOtherProperty', Value: 'value' }], + } as MetricDatum + + assert.throws( + () => validateMetricEvent(metricEvent, true, (_) => false), + /emitted without the `result` property/ + ) + }) + + it('logs warning for metrics missing result property when fatal=false', function () { + const metricEvent: MetricDatum = { + MetricName: 'invalid_metric_no_result', + Value: 1, + Unit: 'None', + Metadata: [{ Key: 'someOtherProperty', Value: 'value' }], + } as MetricDatum + + validateMetricEvent(metricEvent, false, (_) => false) + assertLogsContain('invalid Metric', false, 'warn') + }) + + it('fails validation for metrics with Failed result but missing reason property when fatal=true', function () { + const metricEvent: MetricDatum = { + MetricName: 'invalid_metric_failed_no_reason', + Value: 1, + Unit: 'None', + Metadata: [{ Key: 'result', Value: 'Failed' }], + } as MetricDatum + + assert.throws( + () => validateMetricEvent(metricEvent, true), + /emitted with result=Failed but without the `reason` property/ + ) + }) + + it('logs warning for metrics with Failed result but missing reason property when fatal=false', function () { + const metricEvent: MetricDatum = { + MetricName: 'invalid_metric_failed_no_reason', + Value: 1, + Unit: 'None', + Metadata: [{ Key: 'result', Value: 'Failed' }], + } as MetricDatum + + validateMetricEvent(metricEvent, false) + assertLogsContain('invalid Metric', false, 'warn') + }) + + it('does not fail validation for metrics with missing fields with fatal=true', function () { + const metricEvent: MetricDatum = { + MetricName: 'invalid_metric_missing_fields', + Value: 1, + Unit: 'None', + Metadata: [ + { Key: 'result', Value: 'Succeeded' }, + { Key: 'missingFields', Value: 'field1,field2' }, + ], + } as MetricDatum + + validateMetricEvent(metricEvent, false) + assertLogsContain('invalid Metric', false, 'warn') + }) +}) diff --git a/packages/core/src/test/shared/utilities/collectionUtils.test.ts b/packages/core/src/test/shared/utilities/collectionUtils.test.ts index 53ddc39eff8..34aacb9f28e 100644 --- a/packages/core/src/test/shared/utilities/collectionUtils.test.ts +++ b/packages/core/src/test/shared/utilities/collectionUtils.test.ts @@ -710,8 +710,10 @@ describe('CollectionUtils', async function () { }) describe('partialClone', function () { - it('omits properties by depth', function () { - const testObj = { + let multipleTypedObj: object + + before(async function () { + multipleTypedObj = { a: 34234234234, b: '123456789', c: new Date(2023, 1, 1), @@ -724,57 +726,80 @@ describe('CollectionUtils', async function () { throw Error() }, } + }) - assert.deepStrictEqual(partialClone(testObj, 1), { - ...testObj, + it('omits properties by depth', function () { + assert.deepStrictEqual(partialClone(multipleTypedObj, 1), { + ...multipleTypedObj, d: {}, e: {}, }) - assert.deepStrictEqual(partialClone(testObj, 0, [], '[replaced]'), '[replaced]') - assert.deepStrictEqual(partialClone(testObj, 1, [], '[replaced]'), { - ...testObj, + assert.deepStrictEqual(partialClone(multipleTypedObj, 0, [], { replacement: '[replaced]' }), '[replaced]') + assert.deepStrictEqual(partialClone(multipleTypedObj, 1, [], { replacement: '[replaced]' }), { + ...multipleTypedObj, d: '[replaced]', e: '[replaced]', }) - assert.deepStrictEqual(partialClone(testObj, 3), { - ...testObj, + assert.deepStrictEqual(partialClone(multipleTypedObj, 3), { + ...multipleTypedObj, d: { d1: { d2: {} } }, }) - assert.deepStrictEqual(partialClone(testObj, 4), testObj) + assert.deepStrictEqual(partialClone(multipleTypedObj, 4), multipleTypedObj) }) it('omits properties by name', function () { - const testObj = { - a: 34234234234, - b: '123456789', - c: new Date(2023, 1, 1), - d: { d1: { d2: { d3: 'deep' } } }, + assert.deepStrictEqual(partialClone(multipleTypedObj, 2, ['c', 'e2'], { replacement: '[replaced]' }), { + ...multipleTypedObj, + c: '[replaced]', + d: { d1: '[replaced]' }, + e: { + e1: '[replaced]', + e2: '[replaced]', + }, + }) + assert.deepStrictEqual(partialClone(multipleTypedObj, 3, ['c', 'e2'], { replacement: '[replaced]' }), { + ...multipleTypedObj, + c: '[replaced]', + d: { d1: { d2: '[replaced]' } }, e: { e1: [4, 3, 7], - e2: 'loooooooooo \n nnnnnnnnnnn \n gggggggg \n string', + e2: '[replaced]', }, - f: () => { - throw Error() + }) + }) + + it('truncates properties by maxLength', function () { + const testObj = { + strValue: '1', + boolValue: true, + longString: '11111', + nestedObj: { + nestedObjAgain: { + longNestedStr: '11111', + shortNestedStr: '11', + }, + }, + nestedObj2: { + functionValue: (_: unknown) => {}, }, + nestedObj3: { + myArray: ['1', '11111', '1'], + }, + objInArray: [{ shortString: '11', longString: '11111' }], } - - assert.deepStrictEqual(partialClone(testObj, 2, ['c', 'e2'], '[omitted]'), { + assert.deepStrictEqual(partialClone(testObj, 5, [], { maxStringLength: 2 }), { ...testObj, - c: '[omitted]', - d: { d1: '[omitted]' }, - e: { - e1: '[omitted]', - e2: '[omitted]', + longString: '11...', + nestedObj: { + nestedObjAgain: { + longNestedStr: '11...', + shortNestedStr: '11', + }, }, - }) - assert.deepStrictEqual(partialClone(testObj, 3, ['c', 'e2'], '[omitted]'), { - ...testObj, - c: '[omitted]', - d: { d1: { d2: '[omitted]' } }, - e: { - e1: [4, 3, 7], - e2: '[omitted]', + nestedObj3: { + myArray: ['1', '11...', '1'], }, + objInArray: [{ shortString: '11', longString: '11...' }], }) }) }) diff --git a/packages/core/src/test/shared/utilities/functionUtils.test.ts b/packages/core/src/test/shared/utilities/functionUtils.test.ts index 43da4ebb619..7880d11ff63 100644 --- a/packages/core/src/test/shared/utilities/functionUtils.test.ts +++ b/packages/core/src/test/shared/utilities/functionUtils.test.ts @@ -4,7 +4,7 @@ */ import assert from 'assert' -import { once, onceChanged, debounce } from '../../../shared/utilities/functionUtils' +import { once, onceChanged, debounce, oncePerUniqueArg } from '../../../shared/utilities/functionUtils' import { installFakeClock } from '../../testUtil' describe('functionUtils', function () { @@ -48,6 +48,82 @@ describe('functionUtils', function () { fn('arg1', arg2_) assert.strictEqual(counter, 3) }) + + it('oncePerUniqueArg()', function () { + let counter = 0 + const fn = oncePerUniqueArg((s: string) => { + counter++ + return `processed-${s}` + }) + + const result1 = fn('hello') + assert.strictEqual(result1, 'processed-hello') + assert.strictEqual(counter, 1, 'First call with unique arg should execute') + + const result2 = fn('hello') + assert.strictEqual(result2, undefined) + assert.strictEqual(counter, 1, 'Second call with same arg should not execute') + + const result3 = fn('world') + assert.strictEqual(result3, 'processed-world') + assert.strictEqual(counter, 2, 'Call with new arg should execute') + + fn('hello') + fn('world') + assert.strictEqual(counter, 2, 'Repeated calls with seen args should not execute') + + // New arg should execute + const result4 = fn('test') + assert.strictEqual(result4, 'processed-test') + assert.strictEqual(counter, 3) + }) + + it('oncePerUniqueArg() with custom key', function () { + let counter = 0 + const fn = oncePerUniqueArg( + (_s1: string, _s2: string) => { + counter++ + }, + { key: (s1, _s2) => s1 } + ) + + fn('hello', 'world') + assert.strictEqual(counter, 1, 'First call with unique arg should execute') + + fn('hello', 'worldss') + assert.strictEqual(counter, 1, 'Second arg being different should not execute') + + fn('world', 'hello') + assert.strictEqual(counter, 2, 'First arg being different should execute') + }) + + it('oncePerUniqueArg() with overflow limit', function () { + let counter = 0 + // Create function with small overflow limit + const fn = oncePerUniqueArg( + (_s: string) => { + counter++ + return counter + }, + { overflow: 2 } + ) + + // Fill the buffer + fn('one') + fn('two') + assert.strictEqual(counter, 2) + + fn('three') + assert.strictEqual(counter, 3, '"three" call should execute since it is a new value') + + // 'one' should now be treated as new again since it was evicted + fn('one') + assert.strictEqual(counter, 4, 'one should still be in the buffer') + + // 'three' should still be in the buffer (not executed) + fn('three') + assert.strictEqual(counter, 4, 'three should still be in the buffer') + }) }) describe('debounce', function () { diff --git a/packages/core/src/test/shared/utilities/processUtils.test.ts b/packages/core/src/test/shared/utilities/processUtils.test.ts index 436ac48ecc4..fda6d93293e 100644 --- a/packages/core/src/test/shared/utilities/processUtils.test.ts +++ b/packages/core/src/test/shared/utilities/processUtils.test.ts @@ -10,8 +10,10 @@ import * as sinon from 'sinon' import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities' import { ChildProcess, + ChildProcessOptions, ChildProcessResult, ChildProcessTracker, + defaultProcessWarnThresholds, eof, ProcessStats, } from '../../../shared/utilities/processUtils' @@ -376,8 +378,8 @@ async function stopAndWait(runningProcess: RunningProcess): Promise { await runningProcess.result } -function startSleepProcess(timeout: number = 90): RunningProcess { - const childProcess = new ChildProcess(getSleepCmd(), [timeout.toString()]) +function startSleepProcess(options?: ChildProcessOptions, timeout: number = 90): RunningProcess { + const childProcess = new ChildProcess(getSleepCmd(), [timeout.toString()], options) const result = childProcess.run().catch(() => assert.fail('sleep command threw an error')) return { childProcess, result } } @@ -393,10 +395,6 @@ describe('ChildProcessTracker', function () { usageMock = sinon.stub(ChildProcessTracker.prototype, 'getUsage') }) - beforeEach(function () { - ChildProcessTracker.loggedPids.clear() - }) - afterEach(function () { tracker.clear() usageMock.reset() @@ -449,26 +447,33 @@ describe('ChildProcessTracker', function () { assert.strictEqual(tracker.size, 0, 'expected tracker to be empty') }) - it('logs a warning message when system usage exceeds threshold', async function () { + it('logs a warning message when cpu usage exceeds threshold', async function () { const runningProcess = startSleepProcess() tracker.add(runningProcess.childProcess) const highCpu: ProcessStats = { - cpu: ChildProcessTracker.thresholds.cpu + 1, + cpu: defaultProcessWarnThresholds.cpu + 1, memory: 0, } - const highMemory: ProcessStats = { - cpu: 0, - memory: ChildProcessTracker.thresholds.memory + 1, - } usageMock.returns(highCpu) await clock.tickAsync(ChildProcessTracker.pollingInterval) assertLogsContain('exceeded cpu threshold', false, 'warn') - ChildProcessTracker.loggedPids.clear() + await stopAndWait(runningProcess) + }) + + it('logs a warning message when memory usage exceeds threshold', async function () { + const runningProcess = startSleepProcess() + tracker.add(runningProcess.childProcess) + + const highMemory: ProcessStats = { + cpu: 0, + memory: defaultProcessWarnThresholds.memory + 1, + } usageMock.returns(highMemory) + await clock.tickAsync(ChildProcessTracker.pollingInterval) assertLogsContain('exceeded memory threshold', false, 'warn') @@ -480,7 +485,7 @@ describe('ChildProcessTracker', function () { tracker.add(runningProcess.childProcess) usageMock.returns({ - cpu: ChildProcessTracker.thresholds.cpu + 1, + cpu: defaultProcessWarnThresholds.cpu + 1, memory: 0, }) @@ -492,10 +497,11 @@ describe('ChildProcessTracker', function () { it('does not log for processes within threshold', async function () { const runningProcess = startSleepProcess() + tracker.add(runningProcess.childProcess) usageMock.returns({ - cpu: ChildProcessTracker.thresholds.cpu - 1, - memory: ChildProcessTracker.thresholds.memory - 1, + cpu: defaultProcessWarnThresholds.cpu - 1, + memory: defaultProcessWarnThresholds.memory - 1, }) await clock.tickAsync(ChildProcessTracker.pollingInterval) @@ -504,4 +510,51 @@ describe('ChildProcessTracker', function () { await stopAndWait(runningProcess) }) + + it('respects custom thresholds', async function () { + const largeRunningProcess = startSleepProcess({ + warnThresholds: { + cpu: defaultProcessWarnThresholds.cpu + 10, + memory: defaultProcessWarnThresholds.memory + 10, + }, + }) + tracker.add(largeRunningProcess.childProcess) + const smallRunningProcess = startSleepProcess({ + warnThresholds: { + cpu: defaultProcessWarnThresholds.cpu - 10, + memory: defaultProcessWarnThresholds.memory - 10, + }, + }) + tracker.add(smallRunningProcess.childProcess) + + usageMock.returns({ + cpu: defaultProcessWarnThresholds.cpu + 5, + memory: defaultProcessWarnThresholds.memory + 5, + }) + + await clock.tickAsync(ChildProcessTracker.pollingInterval) + assert.throws(() => assertLogsContain(largeRunningProcess.childProcess.pid().toString(), false, 'warn')) + assertLogsContain(smallRunningProcess.childProcess.pid().toString(), false, 'warn') + + await stopAndWait(largeRunningProcess) + await stopAndWait(smallRunningProcess) + }) + + it('fills custom thresholds with default', async function () { + const runningProcess = startSleepProcess({ + warnThresholds: { + cpu: defaultProcessWarnThresholds.cpu + 10, + }, + }) + tracker.add(runningProcess.childProcess) + + usageMock.returns({ + memory: defaultProcessWarnThresholds.memory + 1, + }) + + await clock.tickAsync(ChildProcessTracker.pollingInterval) + assertLogsContain(runningProcess.childProcess.pid().toString(), false, 'warn') + + await stopAndWait(runningProcess) + }) }) diff --git a/packages/core/src/test/shared/utilities/textDocumentUtilities.test.ts b/packages/core/src/test/shared/utilities/textDocumentUtilities.test.ts new file mode 100644 index 00000000000..e907e09cb41 --- /dev/null +++ b/packages/core/src/test/shared/utilities/textDocumentUtilities.test.ts @@ -0,0 +1,140 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as vscode from 'vscode' +import * as sinon from 'sinon' +import assert from 'assert' +import { createTempUrisForDiff } from '../../../shared/utilities/textDocumentUtilities' +import { DiffContentProvider } from '../../../amazonq/commons/controllers/diffContentProvider' +import fs from '../../../shared/fs/fs' + +describe('textDocumentUtilities', () => { + let sandbox: sinon.SinonSandbox + + beforeEach(() => { + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + sandbox.restore() + }) + + describe('createTempUrisForDiff', () => { + const testScheme = 'test-scheme' + const testFilePath = '/path/to/testFile.js' + const testFileContent = 'line 1\nline 2\nline 3\nline 4\nline 5' + const testMessage = { + code: 'new line 3', + } + const testSelection = new vscode.Selection( + new vscode.Position(2, 0), // Start at line 3 (index 2) + new vscode.Position(2, 6) // End at line 3 (index 2) + ) + + let diffProvider: DiffContentProvider + let fsReadFileTextStub: sinon.SinonStub + + beforeEach(() => { + diffProvider = new DiffContentProvider() + sandbox.stub(diffProvider, 'registerContent') + fsReadFileTextStub = sandbox.stub(fs, 'readFileText').resolves(testFileContent) + }) + + it('should create URIs with the correct scheme and file name', async () => { + const [originalUri, modifiedUri] = await createTempUrisForDiff( + testFilePath, + undefined, + testMessage, + testSelection, + testScheme, + diffProvider + ) + + assert.strictEqual(originalUri.scheme, testScheme) + assert.strictEqual(modifiedUri.scheme, testScheme) + assert.ok(originalUri.path.includes('testFile_original-')) + assert.ok(modifiedUri.path.includes('testFile_proposed-')) + }) + + it('should use provided fileText instead of reading from file when available', async () => { + const providedFileText = 'provided line 1\nprovided line 2\nprovided line 3' + + await createTempUrisForDiff( + testFilePath, + providedFileText, + testMessage, + testSelection, + testScheme, + diffProvider + ) + + // Verify fs.readFileText was not called + assert.strictEqual(fsReadFileTextStub.called, false) + + // Verify the diffProvider was called with the provided content + const registerContentCalls = (diffProvider.registerContent as sinon.SinonStub).getCalls() + assert.strictEqual(registerContentCalls.length, 2) + assert.strictEqual(registerContentCalls[0].args[1], providedFileText) + }) + + it('should read from file when fileText is not provided', async () => { + await createTempUrisForDiff(testFilePath, undefined, testMessage, testSelection, testScheme, diffProvider) + + // Verify fs.readFileText was called with the correct path + assert.strictEqual(fsReadFileTextStub.calledWith(testFilePath), true) + + // Verify the diffProvider was called with the file content + const registerContentCalls = (diffProvider.registerContent as sinon.SinonStub).getCalls() + assert.strictEqual(registerContentCalls.length, 2) + assert.strictEqual(registerContentCalls[0].args[1], testFileContent) + }) + + it('should create modified content by replacing the selected lines', async () => { + await createTempUrisForDiff( + testFilePath, + testFileContent, + testMessage, + testSelection, + testScheme, + diffProvider + ) + + // Verify the diffProvider was called with the correct modified content + const registerContentCalls = (diffProvider.registerContent as sinon.SinonStub).getCalls() + assert.strictEqual(registerContentCalls.length, 2) + + // First call is for original content + assert.strictEqual(registerContentCalls[0].args[1], testFileContent) + + // Second call is for modified content + const expectedModifiedContent = 'line 1\nline 2\nnew line 3\nline 4\nline 5' + assert.strictEqual(registerContentCalls[1].args[1], expectedModifiedContent) + }) + + it('should handle multi-line selections correctly', async () => { + // Selection spanning multiple lines (lines 2-4) + const multiLineSelection = new vscode.Selection( + new vscode.Position(1, 0), // Start at line 2 (index 1) + new vscode.Position(3, 6) // End at line 4 (index 3) + ) + + await createTempUrisForDiff( + testFilePath, + testFileContent, + testMessage, + multiLineSelection, + testScheme, + diffProvider + ) + + // Verify the diffProvider was called with the correct modified content + const registerContentCalls = (diffProvider.registerContent as sinon.SinonStub).getCalls() + + // Expected content should have lines 2-4 replaced with the new code + const expectedModifiedContent = 'line 1\nnew line 3\nline 5' + assert.strictEqual(registerContentCalls[1].args[1], expectedModifiedContent) + }) + }) +}) diff --git a/packages/core/src/test/testAuthUtil.ts b/packages/core/src/test/testAuthUtil.ts new file mode 100644 index 00000000000..4feefec2d68 --- /dev/null +++ b/packages/core/src/test/testAuthUtil.ts @@ -0,0 +1,46 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as sinon from 'sinon' +import * as jose from 'jose' +import * as crypto from 'crypto' +import { LanguageClientAuth } from '../auth/auth2' +import { AuthUtil } from '../codewhisperer/util/authUtil' + +export async function createTestAuthUtil() { + const encryptionKey = crypto.randomBytes(32) + + const jwe = await new jose.CompactEncrypt(new TextEncoder().encode(JSON.stringify({ your: 'mock data' }))) + .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' }) + .encrypt(encryptionKey) + + const fakeToken = { + ssoToken: { + id: 'fake-id', + accessToken: jwe, + }, + updateCredentialsParams: { + data: 'fake-data', + }, + } + + const mockLspAuth: Partial = { + registerSsoTokenChangedHandler: sinon.stub().resolves(), + updateProfile: sinon.stub().resolves(), + getSsoToken: sinon.stub().resolves(fakeToken), + getProfile: sinon.stub().resolves({ + sso_registration_scopes: ['codewhisperer'], + }), + deleteBearerToken: sinon.stub().resolves(), + updateBearerToken: sinon.stub().resolves(), + invalidateSsoToken: sinon.stub().resolves(), + encryptionKey, + } + + // Since AuthUtil is a singleton, we want to remove an existing instance before setting up a new one + AuthUtil.destroy() + + AuthUtil.create(mockLspAuth as LanguageClientAuth) +} diff --git a/packages/core/src/testFixtures/workspaceFolder/QCAFolder/RLinker.java b/packages/core/src/testFixtures/workspaceFolder/QCAFolder/RLinker.java new file mode 100644 index 00000000000..0012ec73813 --- /dev/null +++ b/packages/core/src/testFixtures/workspaceFolder/QCAFolder/RLinker.java @@ -0,0 +1,248 @@ +package recall; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.clyze.utils.ContainerUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** A linker of R-class data. + * + * Given a list of AAR files, this linker extracts R.txt from each + * file and creates the corresponding R classes that are needed so + * that Doop does not report them as phantom classes. This linker does + * not mimic the full logic of the aapt tool, it only generates code + * that is good enough for linking (in the form of a JAR file + * containing all R.java and R*.class files). + * + * This code calls 'javac' and 'jar', so it may fail when these + * programs are not in the path. + */ +public class RLinker { + + private static final String R_AUTOGEN_JAR = "doop-autogen-R.jar"; + + // A map from package names -> nested class -> field -> element + // ids. Used by lookupConst() and XML parsing for layout controls. + private final Map > > constants; + + // A map from package names -> nested class -> set of text + // entries. Used for code generation. + private final Map > > rs; + + // Singleton instance. + private static RLinker instance; + + private RLinker() { + this.constants = new HashMap<>(); + this.rs = new HashMap<>(); + } + + public static RLinker getInstance() { + if (instance == null) + instance = new RLinker(); + return instance; + } + + Integer lookupConst(String packageName, String nestedName, String fld) { + Map > pkgEntry = constants.get(packageName); + if (pkgEntry != null) { + Map fieldEntry = pkgEntry.get(nestedName); + if (fieldEntry != null) { + Integer c = fieldEntry.get(fld); + if (c != null) + return c; + } + } + return null; + } + + /** + * The entry point of the linker. Takes a list of archives + * (containing paths of AAR files) and a map of AAR paths to + * package names. Returns the path of the generated JAR (or null + * if no code generation was done). + */ + String linkRs(String rDir, Set tmpDirs) { + if ((rDir == null) || rs.isEmpty()) { + return null; + } else { + final String tmpDir = ContainerUtils.createTmpDir(tmpDirs); + rs.forEach ((k, v) -> runProcess("javac " + genR(tmpDir, k, v))); + + // Compile JAR and optionally copy to output directory. + String tmpJarName = tmpDir + "/" + R_AUTOGEN_JAR; + runProcess("jar cf " + tmpJarName + " -C " + tmpDir + " ."); + String outJarName = rDir + "/" + R_AUTOGEN_JAR; + try { + FileUtils.copyFile(new File(tmpJarName), new File(outJarName)); + return outJarName; + } catch (IOException ex) { + System.err.println("Failed to copy "); + } + + return tmpJarName; + } + } + + /** + * Given an AAR input and a package name, the R constants are read + * from the R.txt file contained in the archive and the + * appropriate data structures of the linker are filled in. + * + * @param ar The path of the input. + * @param pkg The package name of the input. + */ + public void readRConstants(String ar, String pkg) { + if (!ar.endsWith(".aar")) + return; + try { + String rText = getZipEntry(new ZipFile(ar), "R.txt"); + if (rText != null) { + for (String line : rText.split("\n|\r")) + if (line.length() != 0) + processRLine(ar, line, pkg); + } + } catch (IOException ex) { + System.err.println("Error while reading R.txt: " + ar); + System.err.println(ex.getMessage()); + } + } + + /** + * Process each line in R.txt and (a) generate Java code for later + * use (in 'rs') and (b) remember constant ids (in 'constants'). + * + * @param ar The path of the archive. + * @param line The line of text to be processed. + * @param pkg The package name of the archive. + */ + private void processRLine(String ar, String line, String pkg) { + final String delim = " "; + String[] parts = line.split(delim); + if (parts.length < 2) { + System.err.println("Error processing R.txt"); + } else if (pkg == null) { + System.err.println("WARNING: no package: " + ar); + } else { + + // Extract information from the line text. + String nestedR = parts[1]; + // String rName = pkg + "." + "R$" + nestedR; + String[] newParts = new String[parts.length]; + newParts[0] = parts[0]; + newParts[1] = parts[2]; + newParts[2] = "="; + System.arraycopy(parts, 3, newParts, 3, parts.length - 3); + + // Remember int constants. + if (newParts[0].equals("int") && (newParts.length > 3)) { + String num = newParts[3]; + int val = num.startsWith("0x") ? + (int)(Long.parseLong(num.substring(2), 16)) : + Integer.parseInt(num); + addConstant(pkg, nestedR, newParts[1], val); + } + + // Generate Java code. + Map> pkgEntry = rs.getOrDefault(pkg, new HashMap<>()); + Set set = pkgEntry.getOrDefault(nestedR, new HashSet<>()); + String declaration = " public static "; + boolean first= true; + for (String part: newParts){ + if (first) { + declaration+=part; + first=false; + } else { + declaration+=delim+part; + } + } + declaration+=";"; + set.add(declaration); + pkgEntry.put(nestedR, set); + rs.put(pkg, pkgEntry); + } + } + + /** + * Adds a tuple (packageName, nested, f, c) to 'constants'. + */ + private void addConstant(String packageName, String nested, String f, int c) { + Map> packageEntry = constants.getOrDefault(packageName, new HashMap<>()); + Map nestedEntry = packageEntry.getOrDefault(nested, new HashMap<>()); + Integer val = nestedEntry.get(f); + if (val == null) + nestedEntry.put(f, c); + else if (!val.equals(c)) + System.err.println("WARNING: duplicate values"); + packageEntry.put(nested, nestedEntry); + constants.put(packageName, packageEntry); + } + + private static void runProcess(String cmd) { + try { + Process p = Runtime.getRuntime().exec(cmd); p.waitFor(); int exitVal = p.exitValue(); + if (exitVal != 0) { + System.out.println(cmd + " exit value = " + exitVal); + } + } catch (Exception ex) { + System.err.println("Error invoking command"); + } + } + + private static String genR(String tmpDir, String pkg, + Map> rData) { + String subdir = tmpDir + File.separator + pkg.replaceAll("\\.", File.separator); + if (new File(subdir).mkdirs()) + System.out.println("Created directory: " + subdir); + String rFile = subdir + "/R.java"; + System.out.println("Generating " + rFile); + Collection lines = new ArrayList<>(); + lines.add("// Auto-generated R.java by Doop.\n"); + lines.add("package " + pkg + ";\n"); + lines.add("public final class R {"); + rData.forEach ((k, v) -> genNestedR(k, v, lines)); + lines.add("}"); + + try { + Files.write(Paths.get(rFile), lines, StandardCharsets.UTF_8); + } catch (IOException ex) { + System.err.println("Error generating R class for package: " + pkg); + ex.printStackTrace(); + return null; + } + return rFile; + } + + private static void genNestedR(String nestedName, Collection data, + Collection lines) { + lines.add(" public static final class " + nestedName + " {\n"); + lines.addAll(data); + lines.add(" }\n"); + } + + private static String getZipEntry(ZipFile zip, String entryName) { + try { + Enumeration entries = zip.entries(); + while(entries.hasMoreElements()) { + ZipEntry e = entries.nextElement(); + if (e.getName().equals(entryName)) { + InputStream is = zip.getInputStream(e); + return IOUtils.toString(is, StandardCharsets.UTF_8); + } + } + } catch (IOException ex) { + System.err.println("Error reading zip file"); + } + return null; + } + +} \ No newline at end of file diff --git a/packages/core/webpack.config.js b/packages/core/webpack.config.js index 95827191571..fba19d133b2 100644 --- a/packages/core/webpack.config.js +++ b/packages/core/webpack.config.js @@ -33,6 +33,7 @@ module.exports = (env, argv) => { ...baseVueConfig.createVueEntries(), // The above `createVueEntries` path pattern match does not catch this: 'src/amazonq/webview/ui/amazonq-ui': './src/amazonq/webview/ui/main.ts', + 'src/amazonq/webview/ui/amazonq-ui-connector-adapter': './src/amazonq/webview/ui/connectorAdapter.ts', }, } diff --git a/packages/toolkit/.changes/3.56.0.json b/packages/toolkit/.changes/3.56.0.json new file mode 100644 index 00000000000..58ce02582e1 --- /dev/null +++ b/packages/toolkit/.changes/3.56.0.json @@ -0,0 +1,5 @@ +{ + "date": "2025-04-25", + "version": "3.56.0", + "entries": [] +} \ No newline at end of file diff --git a/packages/toolkit/.changes/3.57.0.json b/packages/toolkit/.changes/3.57.0.json new file mode 100644 index 00000000000..b7deaa57598 --- /dev/null +++ b/packages/toolkit/.changes/3.57.0.json @@ -0,0 +1,10 @@ +{ + "date": "2025-05-01", + "version": "3.57.0", + "entries": [ + { + "type": "Feature", + "description": "AppBuilder: unchecking the 'Attach a debugger' checkbox in local invoke webview invokes the function without a debugger" + } + ] +} \ No newline at end of file diff --git a/packages/toolkit/.changes/3.58.0.json b/packages/toolkit/.changes/3.58.0.json new file mode 100644 index 00000000000..2fdddbec911 --- /dev/null +++ b/packages/toolkit/.changes/3.58.0.json @@ -0,0 +1,5 @@ +{ + "date": "2025-05-02", + "version": "3.58.0", + "entries": [] +} \ No newline at end of file diff --git a/packages/toolkit/.changes/3.59.0.json b/packages/toolkit/.changes/3.59.0.json new file mode 100644 index 00000000000..954cadb9161 --- /dev/null +++ b/packages/toolkit/.changes/3.59.0.json @@ -0,0 +1,5 @@ +{ + "date": "2025-05-05", + "version": "3.59.0", + "entries": [] +} \ No newline at end of file diff --git a/packages/toolkit/.changes/3.60.0.json b/packages/toolkit/.changes/3.60.0.json new file mode 100644 index 00000000000..2464e57a4b0 --- /dev/null +++ b/packages/toolkit/.changes/3.60.0.json @@ -0,0 +1,5 @@ +{ + "date": "2025-05-06", + "version": "3.60.0", + "entries": [] +} \ No newline at end of file diff --git a/packages/toolkit/CHANGELOG.md b/packages/toolkit/CHANGELOG.md index ede66857244..e21988fcd50 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,23 @@ +## 3.60.0 2025-05-06 + +- Miscellaneous non-user-facing changes + +## 3.59.0 2025-05-05 + +- Miscellaneous non-user-facing changes + +## 3.58.0 2025-05-02 + +- Miscellaneous non-user-facing changes + +## 3.57.0 2025-05-01 + +- **Feature** AppBuilder: unchecking the 'Attach a debugger' checkbox in local invoke webview invokes the function without a debugger + +## 3.56.0 2025-04-25 + +- Miscellaneous non-user-facing changes + ## 3.55.0 2025-04-18 - Miscellaneous non-user-facing changes diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 3d7fcdbdbbc..077030c66cb 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -2,7 +2,7 @@ "name": "aws-toolkit-vscode", "displayName": "AWS Toolkit", "description": "Including CodeCatalyst, Infrastructure Composer, and support for Lambda, S3, CloudWatch Logs, CloudFormation, and many other services.", - "version": "3.56.0-SNAPSHOT", + "version": "3.61.0-SNAPSHOT", "extensionKind": [ "workspace" ], @@ -247,13 +247,17 @@ "type": "boolean", "default": false }, + "amazonqLSP": { + "type": "boolean", + "default": true + }, "amazonqLSPInline": { "type": "boolean", "default": false }, "amazonqChatLSP": { "type": "boolean", - "default": false + "default": true } }, "additionalProperties": false