From 726ccd8bc7689623d81bf1a4c644d9ce4937a5fb Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:22:54 -0400 Subject: [PATCH 001/153] feat(amazonq): Add hybrid chat (#7032) ## Problem - we want a chat mode where agents are served by the local extension while the regular "Chat" is served by flare ## Solution - register mynah ui webview providers depending on lsp/normal implementation - temporarily disable the lsp (explain, fix, etc) commands, since they are also registered in the normal "agent" flow - redirect agent messages to the correct chat handler on the UI side - redirect UI messages meant for agents to the extension side - refactor main.ts so that it can be used by both the regular implementation and the lsp one ~(I'll open a seperate PR to merge this into master, since it's going to be a pain to maintain)~ - https://github.com/aws/aws-toolkit-vscode/pull/7033 - pass in references to mynah handlers so that mynah ref injection can happen after in flare - https://github.com/aws/aws-toolkit-vscode/pull/7046 Depends on ~https://github.com/aws/aws-toolkit-vscode/pull/7033~, https://github.com/aws/aws-toolkit-vscode/pull/7046 Related to https://github.com/aws/language-servers/pull/962 --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- package-lock.json | 12 ++ packages/amazonq/src/app/chat/activation.ts | 27 +-- .../src/app/chat/node/activateAgents.ts | 19 ++ packages/amazonq/src/extensionNode.ts | 23 ++- packages/amazonq/src/lsp/chat/activation.ts | 17 +- packages/amazonq/src/lsp/chat/commands.ts | 86 ++++----- packages/amazonq/src/lsp/chat/messages.ts | 10 ++ .../amazonq/src/lsp/chat/webviewProvider.ts | 90 +++++++--- packages/amazonq/src/lsp/client.ts | 2 +- packages/core/package.json | 2 + packages/core/src/amazonq/index.ts | 7 +- packages/core/src/amazonq/indexNode.ts | 13 ++ .../webview/generators/featureConfig.ts | 35 ++++ .../webview/generators/webViewContent.ts | 35 +--- .../webview/messages/messageDispatcher.ts | 169 ++++++++++-------- .../amazonq/webview/ui/connectorAdapter.ts | 98 ++++++++++ packages/core/src/amazonq/webview/ui/main.ts | 29 ++- packages/core/src/shared/index.ts | 1 + packages/core/webpack.config.js | 1 + 19 files changed, 466 insertions(+), 210 deletions(-) create mode 100644 packages/amazonq/src/app/chat/node/activateAgents.ts create mode 100644 packages/core/src/amazonq/indexNode.ts create mode 100644 packages/core/src/amazonq/webview/generators/featureConfig.ts create mode 100644 packages/core/src/amazonq/webview/ui/connectorAdapter.ts diff --git a/package-lock.json b/package-lock.json index 42869391d1a..12c4e38c709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10821,6 +10821,17 @@ "yargs": "^17.0.1" } }, + "node_modules/@aws/chat-client": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@aws/chat-client/-/chat-client-0.1.4.tgz", + "integrity": "sha512-5iqo9f/FjipyWxVPByVcI4yF9NPDOFInuS2ak4bK+j4d6ca1n20CnQrEQcMOdGjl5mde51s7X4Jqvlu3smgHGA==", + "dev": true, + "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", @@ -26792,6 +26803,7 @@ }, "devDependencies": { "@aws-sdk/types": "^3.13.1", + "@aws/chat-client": "^0.1.4", "@aws/chat-client-ui-types": "^0.1.12", "@aws/language-server-runtimes": "^0.2.58", "@aws/language-server-runtimes-types": "^0.1.13", diff --git a/packages/amazonq/src/app/chat/activation.ts b/packages/amazonq/src/app/chat/activation.ts index 10f827814aa..11275188440 100644 --- a/packages/amazonq/src/app/chat/activation.ts +++ b/packages/amazonq/src/app/chat/activation.ts @@ -4,25 +4,14 @@ */ 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 () => { @@ -34,11 +23,6 @@ export async function activate(context: ExtensionContext) { }, 5000) context.subscriptions.push( - window.registerWebviewViewProvider(amazonq.AmazonQChatViewProvider.viewType, provider, { - webviewOptions: { - retainContextWhenHidden: true, - }, - }), amazonq.focusAmazonQChatWalkthrough.register(), amazonq.walkthroughInlineSuggestionsExample.register(), amazonq.walkthroughSecurityScanExample.register(), @@ -64,15 +48,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/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index 945537b38ee..d3e98b025e8 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, @@ -30,6 +30,7 @@ import { beta } from 'aws-core-vscode/dev' import { activate as activateNotifications, NotificationsController } from 'aws-core-vscode/notifications' import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer' 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. @@ -53,9 +54,25 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) { } if (!Experiments.instance.get('amazonqChatLSP', false)) { + 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) const authProvider = new CommonAuthViewProvider( diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index 406b753716f..7cadafadb79 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -8,9 +8,11 @@ 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 { getLogger, globals } from 'aws-core-vscode/shared' +import { activate as registerLegacyChatListeners } from '../../app/chat/activation' +import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' -export function activate(languageClient: LanguageClient, encryptionKey: Buffer, mynahUIPath: string) { +export async function activate(languageClient: LanguageClient, encryptionKey: Buffer, mynahUIPath: string) { const provider = new AmazonQChatViewProvider(mynahUIPath) globals.context.subscriptions.push( @@ -29,6 +31,17 @@ export function activate(languageClient: LanguageClient, encryptionKey: Buffer, registerLanguageServerEventListener(languageClient, provider) provider.onDidResolveWebview(() => { + if (provider.webview) { + DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessageListener().onMessage((msg) => { + provider.webview?.postMessage(msg).then(undefined, (e) => { + getLogger().error('webView.postMessage failed: %s', (e as Error).message) + }) + }) + } + registerMessageListeners(languageClient, provider, encryptionKey) }) + + // register event listeners from the legacy agent flow + await registerLegacyChatListeners(globals.context) } diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index dd495d1bfbf..8a84883b9a7 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -4,26 +4,30 @@ */ import { Commands, globals } from 'aws-core-vscode/shared' -import { window } from 'vscode' +// import { window } from 'vscode' import { AmazonQChatViewProvider } from './webviewProvider' +/** + * 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.sendToPrompt', (data) => { - const triggerType = getCommandTriggerType(data) - const selection = getSelectedText() + // 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.sendToPrompt', (data) => { + // const triggerType = getCommandTriggerType(data) + // const selection = getSelectedText() - void focusAmazonQPanel().then(() => { - void provider.webview?.postMessage({ - command: 'sendToPrompt', - params: { selection: selection, triggerType }, - }) - }) - }), + // void focusAmazonQPanel().then(() => { + // void provider.webview?.postMessage({ + // command: 'sendToPrompt', + // params: { selection: selection, triggerType }, + // }) + // }) + // }), Commands.register('aws.amazonq.openTab', () => { void focusAmazonQPanel().then(() => { void provider.webview?.postMessage({ @@ -35,36 +39,36 @@ export function registerCommands(provider: AmazonQChatViewProvider) { ) } -function getSelectedText(): string { - const editor = window.activeTextEditor - if (editor) { - const selection = editor.selection - const selectedText = editor.document.getText(selection) - return selectedText - } +// function getSelectedText(): string { +// const editor = window.activeTextEditor +// if (editor) { +// const selection = editor.selection +// const selectedText = editor.document.getText(selection) +// return selectedText +// } - return ' ' -} +// return ' ' +// } -function getCommandTriggerType(data: any): string { - // 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 ? 'hotkeys' : 'contextMenu' -} +// function getCommandTriggerType(data: any): string { +// // 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 ? 'hotkeys' : 'contextMenu' +// } -function registerGenericCommand(commandName: string, genericCommand: string, provider: AmazonQChatViewProvider) { - return Commands.register(commandName, (data) => { - const triggerType = getCommandTriggerType(data) - const selection = getSelectedText() +// function registerGenericCommand(commandName: string, genericCommand: string, provider: AmazonQChatViewProvider) { +// return Commands.register(commandName, (data) => { +// const triggerType = getCommandTriggerType(data) +// const selection = getSelectedText() - void focusAmazonQPanel().then(() => { - void provider.webview?.postMessage({ - command: 'genericCommand', - params: { genericCommand, selection, triggerType }, - }) - }) - }) -} +// void focusAmazonQPanel().then(() => { +// void provider.webview?.postMessage({ +// command: 'genericCommand', +// params: { genericCommand, selection, triggerType }, +// }) +// }) +// }) +// } /** * Importing focusAmazonQPanel from aws-core-vscode/amazonq leads to several dependencies down the chain not resolving since AmazonQ chat diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index cf8b4f55940..799b6b2ae47 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -40,6 +40,7 @@ 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 { DefaultAmazonQAppInitContext, messageDispatcher } from 'aws-core-vscode/amazonq' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -73,6 +74,15 @@ export function registerMessageListeners( 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: diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index 426af63739d..2c4d5755434 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -12,9 +12,16 @@ import { WebviewViewResolveContext, Uri, } 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' export class AmazonQChatViewProvider implements WebviewViewProvider { public static readonly viewType = 'aws.amazonq.AmazonQChatView' @@ -23,24 +30,6 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { 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', - }, - ], - }, - ] - constructor(private readonly mynahUIPath: string) {} public async resolveWebviewView( @@ -51,25 +40,60 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { this.webview = webviewView.webview const lspDir = Uri.parse(LanguageServerResolver.defaultDir) + const dist = Uri.joinPath(globals.context.extensionUri, 'dist') webviewView.webview.options = { enableScripts: true, enableCommandUris: true, - localResourceRoots: [lspDir, Uri.parse(path.dirname(this.mynahUIPath))], + localResourceRoots: [lspDir, dist], } + 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 + const connectorAdapterPath = + serverHostname !== undefined + ? Uri.parse(serverHostname) + .with({ path: `/${source}` }) + .toString() + : webviewView.webview.asWebviewUri(Uri.parse(path.join(dist.fsPath, source))).toString() const uiPath = webviewView.webview.asWebviewUri(Uri.parse(this.mynahUIPath)).toString() - webviewView.webview.html = await this.getWebviewContent(uiPath) + webviewView.webview.html = await this.getWebviewContent(uiPath, connectorAdapterPath) this.onDidResolveWebviewEmitter.fire() + performance.mark(amazonqMark.open) } - private async getWebviewContent(mynahUIPath: string) { + private async getWebviewContent(mynahUIPath: string, hybridChatConnector: string) { + 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 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' + : 'https: file+.vscode-resources.vscode-cdn.net' + + const contentPolicy = `default-src ${entrypoint} data: blob: 'unsafe-inline'; + script-src ${entrypoint} filesystem: ws: wss: 'unsafe-inline';` + return ` + Chat - - + + diff --git a/packages/core/package.json b/packages/core/package.json index 60b0be39320..98f24feae81 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -442,7 +442,7 @@ "devDependencies": { "@aws-sdk/types": "^3.13.1", "@aws/chat-client": "^0.1.4", - "@aws/chat-client-ui-types": "^0.1.12", + "@aws/chat-client-ui-types": "^0.1.22", "@aws/language-server-runtimes": "^0.2.58", "@aws/language-server-runtimes-types": "^0.1.13", "@cspotcode/source-map-support": "^0.8.1", diff --git a/packages/core/src/shared/settings-amazonq.gen.ts b/packages/core/src/shared/settings-amazonq.gen.ts index f0a3d47f989..7bd20bc1e78 100644 --- a/packages/core/src/shared/settings-amazonq.gen.ts +++ b/packages/core/src/shared/settings-amazonq.gen.ts @@ -21,7 +21,8 @@ export const amazonqSettings = { "ssoCacheError": {}, "amazonQLspManifestMessage": {}, "amazonQWorkspaceLspManifestMessage": {}, - "amazonQChatDisclaimer": {} + "amazonQChatDisclaimer": {}, + "amazonQChatPairProgramming": {} }, "amazonQ.showCodeWithReferences": {}, "amazonQ.allowFeatureDevelopmentToRunCodeAndTests": {}, From 7afbc71bb230a387fdb07eeff181c5ea070861d0 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Mon, 21 Apr 2025 07:52:15 -0700 Subject: [PATCH 029/153] telemetry(amazonq): correct auth_userState telemetry (#7108) We introduced a new auth state `pendingProfileSelection`, however the current code path will determine the connectivity to be `disconnected` and thus we observe a drastic drop of `connected` users and increase of `disconnected`. ## Problem ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/extensionNode.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index 945537b38ee..71e262846dc 100644 --- a/packages/amazonq/src/extensionNode.ts +++ b/packages/amazonq/src/extensionNode.ts @@ -112,6 +112,11 @@ async function getAuthState(): Promise> { getLogger().error(`Current Amazon Q connection is not SSO, type is: %s`, currConn?.type) } + // Pending profile selection state means users already log in with Sso service + if (authState === 'pendingProfileSelection') { + authState = 'connected' + } + return { authStatus: authState === 'connected' || authState === 'expired' || authState === 'connectedWithNetworkError' From 70ba83fc066cfe8a1f9f50e5eb73a5e1b51d42f4 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Mon, 21 Apr 2025 13:30:17 -0700 Subject: [PATCH 030/153] feat(amazonq): show all customizations across different profiles (#7060) ## Problem followup of #7049 as mentioned in #7049 ``` Before we introduced QProfile customization is bound to a specific idc instance After customization is bound to a specific Q profile and an idc instance can have multi profiles in other words, each Q profile will have access to different sets of customization ``` ## Solution Product team wants us to show all customizations across profiles instead of the connected one only. By that mean, when users select a customization, it might implicitly change the profile for users in the selected customization is not accessible by the current profile. The purpose is to reduce the churn users might be lost what profile has access to what customization. ## user story 1. click "select customization" button from the menu -> should show "all" customizations across profiles 2. select a customization under different profile -> should change the profile to the one owning the newly selected customization 3. select a profile which doesn't have access to the selected customization -> should fallback to "default" and prompt ui saying you don't have access to the customization ### Implicity change profile when user selects a customization under a different profile https://github.com/user-attachments/assets/49bfe61f-04a7-4d07-aaff-1e3c284fb710 ### Change profile should validate if the selected customization is under the new profile or not https://github.com/user-attachments/assets/414f85dd-50a1-4d28-9b33-e59691f25a0c Note that customization will have only 1 profile owner, so there wont be duplicate customization across profiles --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- ...-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json | 4 + .../region/regionProfileManager.test.ts | 56 ++++++++- packages/core/src/codewhisperer/activation.ts | 28 +---- .../src/codewhisperer/client/codewhisperer.ts | 26 +--- packages/core/src/codewhisperer/index.ts | 8 +- .../region/regionProfileManager.ts | 38 ++++-- .../codewhisperer/util/customizationUtil.ts | 111 ++++++++++++++++-- packages/core/src/shared/featureConfig.ts | 27 ++--- .../test/amazonq/customizationUtil.test.ts | 41 +++++++ 9 files changed, 249 insertions(+), 90 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json diff --git a/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json b/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json new file mode 100644 index 00000000000..d3838c1b4d6 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Support selecting customizations across all Q profiles with automatic profile switching for enterprise users" +} diff --git a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index af79f7dc2e5..11441b9bf6f 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -65,7 +65,7 @@ describe('RegionProfileManager', function () { const mockClient = { listAvailableProfiles: listProfilesStub, } - const createClientStub = sinon.stub(sut, 'createQClient').resolves(mockClient) + const createClientStub = sinon.stub(sut, '_createQClient').resolves(mockClient) const r = await sut.listRegionProfile() @@ -234,13 +234,65 @@ describe('RegionProfileManager', function () { }) describe('createQClient', function () { + it(`should configure the endpoint and region from a profile`, async function () { + await setupConnection('idc') + + const iadClient = await sut.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 sut.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 sut.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 sut.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 sut.switchRegionProfile(profileFoo, 'user') assert.deepStrictEqual(sut.activeRegionProfile, profileFoo) const conn = authUtil.conn as SsoConnection - const client = await sut.createQClient('eu-central-1', 'https://amazon.com/', conn) + const client = await sut._createQClient('eu-central-1', 'https://amazon.com/', conn) assert.deepStrictEqual(client.config.region, 'eu-central-1') assert.deepStrictEqual(client.endpoint.href, 'https://amazon.com/') diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index efebb01e179..7a7f2b7b573 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' @@ -343,26 +338,7 @@ 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 - ) - }) - } - }) + auth.regionProfileManager.onDidChangeRegionProfile(onProfileChangedListener) ) // run the auth startup code with context for telemetry diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 2412f6922a8..35f699b24c2 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -7,19 +7,17 @@ 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 { isSsoConnection } from '../../auth/connection' -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' @@ -219,28 +217,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/index.ts b/packages/core/src/codewhisperer/index.ts index 930b168beec..98b7f9239b1 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -99,7 +99,13 @@ 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/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index effb5e3a84b..53c159efdb7 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -47,12 +47,17 @@ 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 @@ -112,7 +117,7 @@ export class RegionProfileManager { } const availableProfiles: RegionProfile[] = [] for (const [region, endpoint] of endpoints.entries()) { - const client = await this.createQClient(region, endpoint, conn as SsoConnection) + const client = await this._createQClient(region, endpoint, conn as SsoConnection) const requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) => client.listAvailableProfiles(request).promise() const request: CodeWhispererUserClient.ListAvailableProfilesRequest = {} @@ -162,7 +167,7 @@ export class RegionProfileManager { const ssoConn = this.connectionProvider() as SsoConnection // 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', @@ -204,13 +209,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() @@ -343,7 +351,21 @@ export class RegionProfileManager { } } - async createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { + // TODO: Should maintain sdk client in a better way + async createQClient(profile: RegionProfile): Promise { + const conn = this.connectionProvider() + if (conn === undefined || !isSsoConnection(conn)) { + 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, conn) + } + + // Visible for testing only, do not use this directly, please use createQClient(profile) + async _createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { const token = (await conn.getToken()).accessToken const serviceOption: ServiceOptions = { apiConfig: userApiConfig, diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index 7898b7a17ba..50ace5379bd 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() + } + } +} /** * @@ -224,7 +287,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', @@ -283,8 +345,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 ) => { @@ -293,8 +359,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) @@ -303,6 +369,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: @@ -333,13 +403,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.listRegionProfile() + 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/shared/featureConfig.ts b/packages/core/src/shared/featureConfig.ts index 6d59fe0782a..d7acb9657be 100644 --- a/packages/core/src/shared/featureConfig.ts +++ b/packages/core/src/shared/featureConfig.ts @@ -4,7 +4,6 @@ */ import { - Customization, FeatureValue, ListFeatureEvaluationsRequest, ListFeatureEvaluationsResponse, @@ -21,7 +20,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() @@ -153,19 +152,7 @@ export class FeatureConfigProvider { if (isBuilderIdConnection(AuthUtil.instance.conn)) { this.featureConfigs.delete(Features.customizationArnOverride) } else if (isIdcSsoConnection(AuthUtil.instance.conn)) { - 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) @@ -176,6 +163,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/test/amazonq/customizationUtil.test.ts b/packages/core/src/test/amazonq/customizationUtil.test.ts index 505c89ae0c9..a3a49e907d9 100644 --- a/packages/core/src/test/amazonq/customizationUtil.test.ts +++ b/packages/core/src/test/amazonq/customizationUtil.test.ts @@ -11,9 +11,11 @@ import { AuthUtil, baseCustomization, Customization, + CustomizationProvider, FeatureConfigProvider, getSelectedCustomization, refreshStatusBar, + RegionProfileManager, setSelectedCustomization, } from '../../codewhisperer' import { FeatureContext, globals } from '../../shared' @@ -23,6 +25,45 @@ import { SsoConnection } from '../../auth' const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' +describe('customizationProvider', function () { + let auth: ReturnType + let ssoConn: SsoConnection + let regionProfileManager: RegionProfileManager + + beforeEach(async () => { + auth = createTestAuth(globals.globalState) + ssoConn = await auth.createInvalidSsoConnection( + createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) + ) + + regionProfileManager = new RegionProfileManager(() => ssoConn) + }) + + 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 ssoConn: SsoConnection From e544eb0eb9fa9f0f11e9ce9b6fdabfff6740c99c Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:19:22 -0400 Subject: [PATCH 031/153] fix(amazonq): temporarily disable q developer profiles (#7118) ## Problem q developer profiles are being fetched by VSCode + flare on the same token causing requests to be throttled ## Solution temporary disable profile fetching. The real solution will need to be implemented in flare, which should disable profile fetching by default for VSCode? --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index b3205d009d6..7e612102c23 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -127,7 +127,7 @@ export async function startLanguageServer( }, awsClientCapabilities: { q: { - developerProfiles: true, + developerProfiles: false, }, window: { notifications: true, From 1e48e367b459754bf8b3047a7051a3a8a8317bb1 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:51:50 -0400 Subject: [PATCH 032/153] feat(amazonq): handle link clicks in /help (#7120) ## Problem links clicked in help and responsible ai policy don't open ## Solution implement info link and link click handlers --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 82bccd68b06..db6ffc8bb0f 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -39,6 +39,9 @@ import { ShowDocumentRequest, contextCommandsNotificationType, ContextCommandParams, + LINK_CLICK_NOTIFICATION_METHOD, + LinkClickParams, + INFO_LINK_CLICK_NOTIFICATION_METHOD, } from '@aws/language-server-runtimes/protocol' import { v4 as uuidv4 } from 'uuid' import * as vscode from 'vscode' @@ -46,7 +49,7 @@ import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vs 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 { AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' import { DefaultAmazonQAppInitContext, messageDispatcher } from 'aws-core-vscode/amazonq' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { @@ -172,6 +175,12 @@ export function registerMessageListeners( } 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 chatRequestType.method: { const chatParams = { ...message.params } as ChatParams const partialResultToken = uuidv4() From 7bb66e109b727736bcd632d230a3e70d4959116c Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Mon, 21 Apr 2025 19:33:45 -0400 Subject: [PATCH 033/153] feat(amazonq): Port in chat message error handling (#7121) ## Problem During the agentic loop, if there are multiple iterations and then it eventually fails, the partial results would not be posted and the user would not be able to continue chat (it wouldn't allow them to type) ## Solution Port in the VSC client related change from: https://github.com/aws/language-servers/pull/1012 Now if there are errors during the agentic loop, we will display all the partial responses we have gathered and then break out of the generating state, allowing the user to continue. ## How I tested Between the previous and new changes I ran the same test suite - Ask agentic chat to make 101 files (since [a recent change](https://github.com/aws/language-servers/pull/1022) increased the loop limit to 100) in a test folder, adding in some random text - Ask agentic chat to "read every single file, not skipping any of them, and then display the text of each one". - W/ the old changes it would error part way and the UI would show "generating" and the user could not do anything - W/ the new changes it will show partial results (the text of the files it could resolve) and then allow the user to continue with another prompt. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/amazonq/src/lsp/chat/messages.ts | 61 +++++++++++++++++------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index db6ffc8bb0f..2f17d6a8bfa 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -182,10 +182,24 @@ export function registerMessageListeners( break } case chatRequestType.method: { - const chatParams = { ...message.params } as ChatParams + const chatParams: ChatParams = { ...message.params } const partialResultToken = uuidv4() - const chatDisposable = languageClient.onProgress(chatRequestType, partialResultToken, (partialResult) => - handlePartialResult(partialResult, encryptionKey, provider, chatParams.tabId) + let lastPartialResult: ChatResult | undefined + 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 = @@ -197,17 +211,36 @@ export function registerMessageListeners( } const chatRequest = await encryptRequest(chatParams, encryptionKey) - const chatResult = (await languageClient.sendRequest(chatRequestType.method, { - ...chatRequest, - partialResultToken, - })) as string | ChatResult - void handleCompleteResult( - chatResult, - encryptionKey, - provider, - chatParams.tabId, - chatDisposable - ) + try { + const chatResult = await languageClient.sendRequest(chatRequestType.method, { + ...chatRequest, + partialResultToken, + }) + await handleCompleteResult( + chatResult, + encryptionKey, + provider, + chatParams.tabId, + chatDisposable + ) + } catch (e) { + languageClient.info(`Error occurred during chat request: ${e}`) + // Use the last partial result if available, append error message + const errorResult: ChatResult = { + ...lastPartialResult, + body: lastPartialResult?.body + ? `${lastPartialResult.body}\n\n ❌ Error: Request failed to complete` + : '❌ An error occurred while processing your request', + } + + await handleCompleteResult( + errorResult, + encryptionKey, + provider, + chatParams.tabId, + chatDisposable + ) + } break } case quickActionRequestType.method: { From ccbdf48435e6b35ee833fb4f5653128bc75438e2 Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Mon, 21 Apr 2025 18:05:40 -0700 Subject: [PATCH 034/153] feat(lsp): add handler for openFileDiff notification (#7119) ## Problem Handler is missing for openFileDiff lsp workspace command ## Solution Add notification handler, extend the existing viewDiff implementation by passing in the final content for the temp file --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 21 ++++++++++++++++++++- packages/core/src/amazonq/index.ts | 1 + 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 2f17d6a8bfa..308e1e13a0d 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -39,6 +39,8 @@ import { ShowDocumentRequest, contextCommandsNotificationType, ContextCommandParams, + openFileDiffNotificationType, + OpenFileDiffParams, LINK_CLICK_NOTIFICATION_METHOD, LinkClickParams, INFO_LINK_CLICK_NOTIFICATION_METHOD, @@ -50,7 +52,7 @@ import * as jose from 'jose' import { AmazonQChatViewProvider } from './webviewProvider' import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' -import { DefaultAmazonQAppInitContext, messageDispatcher } from 'aws-core-vscode/amazonq' +import { DefaultAmazonQAppInitContext, messageDispatcher, EditorContentController } from 'aws-core-vscode/amazonq' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -395,6 +397,23 @@ export function registerMessageListeners( params: params, }) }) + + languageClient.onNotification(openFileDiffNotificationType.method, async (params: OpenFileDiffParams) => { + const edc = 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) + ) + await edc.viewDiff({ + context: { + activeFileContext: { filePath: params.originalFileUri }, + focusAreaContext: { selectionInsideExtendedCodeBlock: entireDocumentSelection }, + }, + code: params.fileContent, + }) + }) } function isServerEvent(command: string) { diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index e38eec98035..8a4815c1577 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -47,6 +47,7 @@ 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 } from './commons/controllers/contentController' /** * main from createMynahUI is a purely browser dependency. Due to this From 3b619b0043a8d52e8d5b132b5aadf097edcc3d28 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Mon, 21 Apr 2025 21:23:00 -0400 Subject: [PATCH 035/153] feat(amazonq): handle stop response (#7122) ## Problem agentic chat has stop response disabled ## Solution make it so that when its clicked the token is disposed off and the UI unlocks --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- package-lock.json | 16 ++++++------- packages/amazonq/src/lsp/chat/messages.ts | 29 +++++++++++++++++++---- packages/core/package.json | 2 +- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 77bfc670449..1050abd6a36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10833,12 +10833,12 @@ } }, "node_modules/@aws/chat-client-ui-types": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@aws/chat-client-ui-types/-/chat-client-ui-types-0.1.22.tgz", - "integrity": "sha512-vn+UKnh9hgZN1LCMONgeZE8WWxivWXaHQq+oG9wpbFhaTXn/nNBTQ9ON7S2fvMqo0g0Np/6hirxZy5ROcWnB9Q==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@aws/chat-client-ui-types/-/chat-client-ui-types-0.1.25.tgz", + "integrity": "sha512-sxSookCLlhfsamse3x9AkvCei7SSUYDOklAe1O2jiUOYSN79M5JlVVRZShoqiOCHds7bb9nSaz+DMWIwEK1+2w==", "dev": true, "dependencies": { - "@aws/language-server-runtimes-types": "^0.1.19" + "@aws/language-server-runtimes-types": "^0.1.21" } }, "node_modules/@aws/language-server-runtimes": { @@ -10876,9 +10876,9 @@ } }, "node_modules/@aws/language-server-runtimes-types": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.19.tgz", - "integrity": "sha512-c81J3G3N6JP5A6g70xTpK/XPS1YWwviQBn307Rk3S5fSiALT8INeHM+IPDg9AuONU6w378RJjzQy3+PE0gJvsw==", + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.21.tgz", + "integrity": "sha512-03C3dz4MvMyKg4UAgHMNNw675OQJkDq+7TPXUPaiasqPF946ywTDD9xoNPaVOQI+YTtC7Re4vhPRfBzyad3MOg==", "dev": true, "dependencies": { "vscode-languageserver-textdocument": "^1.0.12", @@ -26804,7 +26804,7 @@ "devDependencies": { "@aws-sdk/types": "^3.13.1", "@aws/chat-client": "^0.1.4", - "@aws/chat-client-ui-types": "^0.1.22", + "@aws/chat-client-ui-types": "^0.1.24", "@aws/language-server-runtimes": "^0.2.58", "@aws/language-server-runtimes-types": "^0.1.13", "@cspotcode/source-map-support": "^0.8.1", diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 308e1e13a0d..7365f10445a 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -14,6 +14,8 @@ import { UiMessageResultParams, CHAT_PROMPT_OPTION_ACKNOWLEDGED, ChatPromptOptionAcknowledgedMessage, + STOP_CHAT_RESPONSE, + StopChatResponseMessage, } from '@aws/chat-client-ui-types' import { ChatResult, @@ -44,6 +46,7 @@ import { LINK_CLICK_NOTIFICATION_METHOD, LinkClickParams, INFO_LINK_CLICK_NOTIFICATION_METHOD, + CancellationTokenSource, } from '@aws/language-server-runtimes/protocol' import { v4 as uuidv4 } from 'uuid' import * as vscode from 'vscode' @@ -99,6 +102,7 @@ export function registerMessageListeners( 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`) @@ -183,10 +187,21 @@ export function registerMessageListeners( 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() let lastPartialResult: ChatResult | undefined + const cancellationToken = new CancellationTokenSource() + chatStreamTokens.set(chatParams.tabId, cancellationToken) + const chatDisposable = languageClient.onProgress( chatRequestType, partialResultToken, @@ -214,10 +229,14 @@ export function registerMessageListeners( const chatRequest = await encryptRequest(chatParams, encryptionKey) try { - const chatResult = await languageClient.sendRequest(chatRequestType.method, { - ...chatRequest, - partialResultToken, - }) + const chatResult = await languageClient.sendRequest( + chatRequestType.method, + { + ...chatRequest, + partialResultToken, + }, + cancellationToken.token + ) await handleCompleteResult( chatResult, encryptionKey, @@ -242,6 +261,8 @@ export function registerMessageListeners( chatParams.tabId, chatDisposable ) + } finally { + chatStreamTokens.delete(chatParams.tabId) } break } diff --git a/packages/core/package.json b/packages/core/package.json index 98f24feae81..b9261d971dc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -442,7 +442,7 @@ "devDependencies": { "@aws-sdk/types": "^3.13.1", "@aws/chat-client": "^0.1.4", - "@aws/chat-client-ui-types": "^0.1.22", + "@aws/chat-client-ui-types": "^0.1.24", "@aws/language-server-runtimes": "^0.2.58", "@aws/language-server-runtimes-types": "^0.1.13", "@cspotcode/source-map-support": "^0.8.1", From 2055bbfdf1a6e95e4fbce432c78ec223ceec6b93 Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Tue, 22 Apr 2025 05:05:19 -0700 Subject: [PATCH 036/153] feat(amazonq): option to show diff in reverse order (#7126) ## Problem Sometimes we need to show the diff in reverse order (left/right flipped). ## Solution Add a flag to show the diff in reverse order. Right now we will always do this in the lsp diff handler to avoid the need to create a temporary file. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 20 +++++++++++-------- .../commons/controllers/contentController.ts | 5 ++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 7365f10445a..3b23a525e95 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -54,7 +54,7 @@ import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vs import * as jose from 'jose' import { AmazonQChatViewProvider } from './webviewProvider' import { AuthUtil } from 'aws-core-vscode/codewhisperer' -import { AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' +import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' import { DefaultAmazonQAppInitContext, messageDispatcher, EditorContentController } from 'aws-core-vscode/amazonq' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { @@ -420,20 +420,24 @@ export function registerMessageListeners( }) languageClient.onNotification(openFileDiffNotificationType.method, async (params: OpenFileDiffParams) => { - const edc = new EditorContentController() + 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) ) - await edc.viewDiff({ - context: { - activeFileContext: { filePath: params.originalFileUri }, - focusAreaContext: { selectionInsideExtendedCodeBlock: entireDocumentSelection }, + await ecc.viewDiff( + { + context: { + activeFileContext: { filePath: params.originalFileUri }, + focusAreaContext: { selectionInsideExtendedCodeBlock: entireDocumentSelection }, + }, + code: params.fileContent, }, - code: params.fileContent, - }) + amazonQDiffScheme, + true + ) }) } diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index 64e1254c21a..edb9ac7bd87 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -156,7 +156,7 @@ export class EditorContentController { * * @param message the message from Amazon Q chat */ - public async viewDiff(message: any, scheme: string = amazonQDiffScheme) { + public async viewDiff(message: any, scheme: string = amazonQDiffScheme, reverseOrder = false) { const errorNotification = 'Unable to Open Diff.' const { filePath, selection } = extractFileAndCodeSelectionFromMessage(message) @@ -170,8 +170,7 @@ export class EditorContentController { const disposable = vscode.workspace.registerTextDocumentContentProvider(scheme, contentProvider) await vscode.commands.executeCommand( 'vscode.diff', - originalFileUri, - uri, + ...(reverseOrder ? [uri, originalFileUri] : [originalFileUri, uri]), `${path.basename(filePath)} ${amazonQTabSuffix}` ) From 52cc07ff66fb5b7399746ab7d9cce3baee2cf821 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Tue, 22 Apr 2025 08:23:52 -0400 Subject: [PATCH 037/153] fix(amazonq): increase polling frequency for bearer token. (#7123) ## Problem During the bug bash, someone encountered the LSP attempting to use an expired bearer token. I am unable to reproduce the issue, but can speculate at the cause. We refresh the bearer token on the LSP every 1 minute, but if the token expires, and then a request is made before the next refresh it can fail. Note: this is a temporary solution until we have LSP auth. ## Solution - double the frequency with which we check the token. - We still only send the token if it changed, meaning this won't cause noisy request to LSP. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/auth.ts b/packages/amazonq/src/lsp/auth.ts index 0637019a1ab..4eb92e40788 100644 --- a/packages/amazonq/src/lsp/auth.ts +++ b/packages/amazonq/src/lsp/auth.ts @@ -86,7 +86,7 @@ export class AmazonQLspAuth { this.client.info(`UpdateBearerToken: ${JSON.stringify(request)}`) } - public startTokenRefreshInterval(pollingTime: number = oneMinute) { + public startTokenRefreshInterval(pollingTime: number = oneMinute / 2) { const interval = setInterval(async () => { await this.refreshConnection().catch((e) => { getLogger('amazonqLsp').error('Unable to update bearer token: %s', (e as Error).message) From 1b107ab6ef9b30c2860575fd3f8d62fa1ad3650d Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Tue, 22 Apr 2025 08:55:23 -0400 Subject: [PATCH 038/153] feat(amazonq): add handling for button clicks (#7125) ## Problem With https://github.com/aws/language-servers/pull/1037, the chat client will forward the button click event to the host client (VSC in this case). We are then responsible for making a request to the LSP with this button click. ## Solution - route the button click to the LSP. - log any failed button click actions. ## Verification See https://github.com/aws/language-servers/pull/1037, for a demo of the e2e button hookup working. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- package-lock.json | 1500 +++++++-------------- packages/amazonq/src/lsp/chat/messages.ts | 15 +- packages/core/package.json | 4 +- 3 files changed, 508 insertions(+), 1011 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1050abd6a36..a91b1d9aba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -10807,8 +10761,6 @@ }, "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==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -10823,9 +10775,8 @@ }, "node_modules/@aws/chat-client": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@aws/chat-client/-/chat-client-0.1.4.tgz", - "integrity": "sha512-5iqo9f/FjipyWxVPByVcI4yF9NPDOFInuS2ak4bK+j4d6ca1n20CnQrEQcMOdGjl5mde51s7X4Jqvlu3smgHGA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws/chat-client-ui-types": "^0.1.12", "@aws/language-server-runtimes-types": "^0.1.10", @@ -10833,24 +10784,24 @@ } }, "node_modules/@aws/chat-client-ui-types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@aws/chat-client-ui-types/-/chat-client-ui-types-0.1.25.tgz", - "integrity": "sha512-sxSookCLlhfsamse3x9AkvCei7SSUYDOklAe1O2jiUOYSN79M5JlVVRZShoqiOCHds7bb9nSaz+DMWIwEK1+2w==", + "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.21" + "@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", @@ -10864,7 +10815,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", @@ -10876,10 +10827,11 @@ } }, "node_modules/@aws/language-server-runtimes-types": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.21.tgz", - "integrity": "sha512-03C3dz4MvMyKg4UAgHMNNw675OQJkDq+7TPXUPaiasqPF946ywTDD9xoNPaVOQI+YTtC7Re4vhPRfBzyad3MOg==", + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.22.tgz", + "integrity": "sha512-cyNrq6TqCcD9+vYUvvXJ5EJzfB4DrLtDBzBXgv/4zPIMRH0YwGEsRZLzPDwCPCxuZ5kGlal3GlBMkLkMCRGPdQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5" @@ -10887,8 +10839,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": { @@ -10938,8 +10888,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": { @@ -10988,8 +10936,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": { @@ -11011,8 +10957,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": { @@ -11028,8 +10972,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": { @@ -11050,8 +10992,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": { @@ -11074,8 +11014,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": { @@ -11092,8 +11030,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": { @@ -11112,8 +11048,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": { @@ -11128,8 +11062,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": { @@ -11143,8 +11075,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": { @@ -11159,8 +11089,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": { @@ -11178,8 +11106,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": { @@ -11196,8 +11122,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": { @@ -11214,8 +11138,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": { @@ -11228,8 +11150,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": { @@ -11244,8 +11164,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": { @@ -11257,8 +11175,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": { @@ -11282,8 +11198,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": { @@ -11296,8 +11210,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": { @@ -11313,8 +11225,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": { @@ -11333,8 +11243,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": { @@ -11350,8 +11258,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": { @@ -11367,8 +11273,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": { @@ -11383,8 +11287,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": { @@ -11397,8 +11299,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": { @@ -11410,8 +11310,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": { @@ -11425,8 +11323,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": { @@ -11445,8 +11341,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": { @@ -11466,8 +11360,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": { @@ -11480,8 +11372,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": { @@ -11494,8 +11384,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": { @@ -11510,8 +11398,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": { @@ -11527,8 +11413,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": { @@ -11541,8 +11425,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": { @@ -11555,8 +11437,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": { @@ -11570,8 +11450,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": { @@ -11584,8 +11462,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": { @@ -11597,8 +11473,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": { @@ -11611,8 +11485,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": { @@ -11631,8 +11503,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": { @@ -11650,8 +11520,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": { @@ -11663,8 +11531,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": { @@ -11678,8 +11544,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": { @@ -11693,8 +11557,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": { @@ -11706,8 +11568,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": { @@ -11719,8 +11579,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": { @@ -11733,8 +11591,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": { @@ -11746,8 +11602,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": { @@ -11763,8 +11617,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": { @@ -11782,8 +11634,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": { @@ -11797,8 +11647,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": { @@ -11810,8 +11658,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": { @@ -11824,8 +11670,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": { @@ -11839,8 +11683,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": { @@ -11859,8 +11701,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": { @@ -11872,8 +11712,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": { @@ -11886,8 +11724,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": { @@ -11902,35 +11738,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" }, @@ -11940,9 +11771,8 @@ }, "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" @@ -11950,9 +11780,8 @@ }, "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==", "hasInstallScript": true, + "license": "Apache License 2.0", "dependencies": { "escape-html": "^1.0.3", "highlight.js": "^11.11.0", @@ -12278,8 +12107,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": { @@ -12292,8 +12119,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": { @@ -12486,8 +12311,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": { @@ -12568,8 +12391,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": { @@ -12578,8 +12399,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": { @@ -12591,8 +12410,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": { @@ -12604,8 +12421,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": { @@ -12620,8 +12435,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": { @@ -12630,8 +12443,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": { @@ -12651,8 +12462,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": { @@ -12671,8 +12480,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": { @@ -12693,8 +12500,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": { @@ -12716,8 +12521,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": { @@ -12736,8 +12539,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": { @@ -12757,8 +12558,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": { @@ -12775,8 +12574,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": { @@ -12797,8 +12594,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": { @@ -12817,8 +12612,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": { @@ -12837,8 +12630,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": { @@ -12856,8 +12647,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": { @@ -12866,8 +12655,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": { @@ -12887,8 +12674,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": { @@ -12904,8 +12689,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": { @@ -12923,8 +12706,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": { @@ -12945,8 +12726,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": { @@ -12961,8 +12740,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": { @@ -12977,8 +12754,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": { @@ -12994,8 +12769,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": { @@ -13004,8 +12777,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": { @@ -13022,8 +12793,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": { @@ -13039,8 +12808,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": { @@ -13074,8 +12841,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": { @@ -13084,8 +12849,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": { @@ -13102,8 +12865,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": { @@ -13112,8 +12873,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": { @@ -13133,8 +12892,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": { @@ -13174,36 +12931,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": { @@ -13213,36 +12960,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" }, @@ -14377,13 +14114,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, @@ -14558,8 +14288,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" }, @@ -14582,9 +14310,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", @@ -14715,8 +14442,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" }, @@ -16153,8 +15878,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": { @@ -16876,8 +16599,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" }, @@ -18844,8 +18565,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": [ { @@ -19622,8 +19341,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": { @@ -19916,8 +19633,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": { @@ -20102,8 +19817,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": { @@ -20158,8 +19871,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" }, @@ -21114,8 +20825,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" }, @@ -21157,13 +20866,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" }, @@ -21195,8 +20901,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": { @@ -21779,8 +21483,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" }, @@ -22530,8 +22232,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": { @@ -22905,8 +22605,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", @@ -22950,8 +22648,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" }, @@ -23472,8 +23168,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": { @@ -23487,8 +23181,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": { @@ -23505,8 +23197,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" }, @@ -23517,8 +23207,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": { @@ -23697,8 +23385,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": { @@ -24095,8 +23781,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" }, @@ -25006,9 +24690,8 @@ }, "node_modules/ts-node": { "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -25184,8 +24867,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": { @@ -25254,8 +24935,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": { @@ -25536,8 +25215,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", @@ -26227,8 +25905,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", @@ -26241,8 +25917,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": { @@ -26805,8 +26479,8 @@ "@aws-sdk/types": "^3.13.1", "@aws/chat-client": "^0.1.4", "@aws/chat-client-ui-types": "^0.1.24", - "@aws/language-server-runtimes": "^0.2.58", - "@aws/language-server-runtimes-types": "^0.1.13", + "@aws/language-server-runtimes": "^0.2.70", + "@aws/language-server-runtimes-types": "^0.1.21", "@cspotcode/source-map-support": "^0.8.1", "@sinonjs/fake-timers": "^10.0.2", "@types/adm-zip": "^0.4.34", @@ -26982,8 +26656,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", @@ -27002,8 +26674,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", @@ -27018,8 +26688,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", @@ -27031,8 +26699,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" @@ -27043,8 +26709,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", @@ -27068,8 +26732,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", @@ -27088,8 +26750,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", @@ -27104,8 +26764,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", @@ -27117,8 +26775,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" @@ -27129,8 +26785,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", @@ -27252,8 +26906,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", @@ -27272,8 +26924,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", @@ -27288,8 +26938,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", @@ -27301,8 +26949,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" @@ -27313,8 +26959,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", @@ -27338,8 +26982,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", @@ -27358,8 +27000,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", @@ -27374,8 +27014,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", @@ -27387,8 +27025,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" @@ -27399,8 +27035,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", @@ -27473,8 +27107,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", @@ -27493,8 +27125,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", @@ -27509,8 +27139,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", @@ -27522,8 +27150,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" @@ -27534,8 +27160,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", @@ -27568,8 +27192,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", @@ -27611,8 +27233,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", @@ -27627,8 +27247,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", @@ -27664,8 +27282,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", @@ -27698,8 +27314,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", @@ -27728,8 +27342,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", @@ -27771,8 +27383,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", @@ -27809,8 +27419,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", @@ -27838,8 +27446,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", @@ -27867,8 +27473,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", @@ -27912,8 +27516,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", @@ -27970,8 +27572,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", @@ -27986,8 +27586,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" @@ -27998,8 +27596,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", @@ -28012,8 +27608,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" @@ -28024,8 +27618,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", @@ -28038,8 +27630,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", @@ -28051,8 +27641,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", @@ -28074,8 +27662,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", @@ -28094,8 +27680,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", @@ -28113,8 +27697,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" @@ -28125,8 +27707,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", @@ -28144,8 +27724,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", @@ -28157,8 +27735,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", @@ -28170,8 +27746,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", @@ -28185,8 +27759,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", @@ -28198,8 +27770,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", @@ -28216,8 +27786,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" @@ -28228,8 +27796,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", @@ -28242,8 +27808,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", @@ -28256,8 +27820,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" @@ -28268,8 +27830,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", @@ -28281,8 +27841,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", @@ -28294,8 +27852,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", @@ -28313,8 +27869,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", @@ -28326,8 +27880,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", @@ -28342,8 +27894,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", @@ -28355,8 +27905,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", @@ -28369,8 +27917,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" @@ -28381,8 +27927,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", @@ -28394,8 +27938,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" @@ -28406,8 +27948,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", @@ -28419,8 +27959,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" @@ -28431,8 +27969,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" @@ -28443,8 +27979,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" @@ -28455,8 +27989,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", @@ -28468,8 +28000,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" @@ -28491,8 +28021,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" @@ -28503,8 +28031,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", @@ -28517,8 +28043,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" @@ -28529,8 +28053,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" @@ -28650,6 +28172,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", @@ -28757,8 +28291,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", @@ -28806,8 +28338,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", @@ -28828,8 +28358,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", @@ -28844,8 +28372,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", @@ -28865,8 +28391,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", @@ -28889,8 +28413,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", @@ -28912,8 +28434,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", @@ -28929,8 +28449,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", @@ -28948,8 +28466,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", @@ -28965,8 +28481,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", @@ -28980,8 +28494,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", @@ -28994,8 +28506,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", @@ -29009,8 +28519,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", @@ -29027,8 +28535,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", @@ -29076,8 +28582,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", @@ -29093,8 +28597,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", @@ -29110,8 +28612,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", @@ -29123,8 +28623,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", @@ -29137,19 +28635,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", @@ -29160,8 +28656,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", @@ -29183,7 +28677,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": { @@ -29194,12 +28688,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": { @@ -29207,15 +28699,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": { @@ -29223,17 +28713,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" }, @@ -29242,15 +28730,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": { @@ -29258,13 +28744,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" }, @@ -29273,13 +28757,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": { @@ -29287,12 +28769,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": { @@ -29300,13 +28780,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": { @@ -29314,13 +28792,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": { @@ -29328,14 +28804,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" }, @@ -29344,12 +28818,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" @@ -29359,12 +28831,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": { @@ -29373,8 +28843,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" @@ -29384,13 +28852,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": { @@ -29398,18 +28864,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": { @@ -29417,18 +28881,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" }, @@ -29437,12 +28899,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": { @@ -29450,12 +28910,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": { @@ -29463,14 +28921,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": { @@ -29478,15 +28934,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": { @@ -29494,12 +28948,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": { @@ -29507,12 +28959,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": { @@ -29520,12 +28970,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" }, @@ -29534,12 +28982,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": { @@ -29547,24 +28993,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": { @@ -29572,16 +29014,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" @@ -29591,17 +29031,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": { @@ -29609,9 +29047,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" @@ -29621,13 +29057,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": { @@ -29636,8 +29070,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", @@ -29650,8 +29082,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" @@ -29662,8 +29092,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" @@ -29674,8 +29102,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", @@ -29687,8 +29113,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" @@ -29698,14 +29122,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" }, @@ -29714,17 +29136,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": { @@ -29732,13 +29152,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": { @@ -29747,8 +29165,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" @@ -29758,12 +29174,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": { @@ -29771,28 +29185,24 @@ } }, "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", - "tslib": "^2.6.2" + "@smithy/service-error-classification": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, "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", @@ -29805,8 +29215,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" @@ -29817,8 +29225,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", @@ -29828,10 +29234,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": { @@ -29869,6 +29278,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, @@ -29985,7 +29398,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": { @@ -30034,6 +29447,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, @@ -30069,6 +29490,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, @@ -30091,6 +29523,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, @@ -30131,6 +29577,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, @@ -30154,6 +29605,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, @@ -30185,7 +29655,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": { @@ -30240,7 +29710,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": { @@ -30257,6 +29733,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, @@ -30269,10 +29756,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" }, @@ -30399,6 +29896,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", @@ -30506,8 +30015,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", @@ -30528,8 +30035,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", @@ -30543,8 +30048,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", @@ -30557,8 +30060,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", @@ -30572,8 +30073,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", @@ -30590,8 +30089,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", @@ -30639,8 +30136,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", @@ -30656,8 +30151,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", @@ -30673,8 +30166,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", @@ -30686,8 +30177,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", @@ -30700,19 +30189,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", @@ -30723,8 +30210,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", @@ -30746,7 +30231,7 @@ } }, "src.gen/@amzn/codewhisperer-streaming/node_modules/@babel/runtime": { - "version": "7.26.0", + "version": "7.26.9", "dev": true, "license": "MIT", "dependencies": { @@ -30757,12 +30242,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": { @@ -30770,15 +30253,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": { @@ -30786,17 +30267,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" }, @@ -30805,15 +30284,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": { @@ -30821,13 +30298,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" }, @@ -30836,13 +30311,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": { @@ -30850,12 +30323,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": { @@ -30863,13 +30334,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": { @@ -30877,13 +30346,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": { @@ -30891,14 +30358,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" }, @@ -30907,12 +30372,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" @@ -30922,12 +30385,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": { @@ -30936,8 +30397,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" @@ -30947,13 +30406,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": { @@ -30961,18 +30418,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": { @@ -30980,18 +30435,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" }, @@ -31000,12 +30453,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": { @@ -31013,12 +30464,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": { @@ -31026,14 +30475,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": { @@ -31041,15 +30488,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": { @@ -31057,12 +30502,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": { @@ -31070,12 +30513,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": { @@ -31083,12 +30524,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" }, @@ -31097,12 +30536,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": { @@ -31110,24 +30547,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": { @@ -31135,16 +30568,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" @@ -31154,17 +30585,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": { @@ -31172,9 +30601,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" @@ -31184,13 +30611,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": { @@ -31199,8 +30624,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", @@ -31213,8 +30636,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" @@ -31225,8 +30646,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" @@ -31237,8 +30656,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", @@ -31250,8 +30667,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" @@ -31261,14 +30676,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" }, @@ -31277,17 +30690,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": { @@ -31295,13 +30706,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": { @@ -31310,8 +30719,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" @@ -31321,12 +30728,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": { @@ -31334,13 +30739,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": { @@ -31348,14 +30751,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", @@ -31368,8 +30769,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" @@ -31380,8 +30779,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", @@ -31391,10 +30788,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": { @@ -31552,7 +30952,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": { @@ -31601,6 +31001,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, @@ -31636,6 +31044,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, @@ -31658,6 +31077,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, @@ -31698,6 +31131,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, @@ -31721,6 +31159,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, @@ -31752,7 +31209,7 @@ "license": "0BSD" }, "src.gen/@amzn/codewhisperer-streaming/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "dev": true, "license": "ISC", "bin": { @@ -31807,7 +31264,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": { @@ -31824,6 +31287,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, @@ -31836,10 +31310,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/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 3b23a525e95..da84162f2ec 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -46,6 +46,8 @@ import { LINK_CLICK_NOTIFICATION_METHOD, LinkClickParams, INFO_LINK_CLICK_NOTIFICATION_METHOD, + buttonClickRequestType, + ButtonClickResult, CancellationTokenSource, } from '@aws/language-server-runtimes/protocol' import { v4 as uuidv4 } from 'uuid' @@ -304,6 +306,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) @@ -505,7 +519,6 @@ async function handleCompleteResult( ) { const decryptedMessage = typeof result === 'string' && encryptionKey ? await decodeRequest(result, encryptionKey) : result - void provider.webview?.postMessage({ command: chatRequestType.method, params: decryptedMessage, diff --git a/packages/core/package.json b/packages/core/package.json index b9261d971dc..5d60b72cc58 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -443,8 +443,8 @@ "@aws-sdk/types": "^3.13.1", "@aws/chat-client": "^0.1.4", "@aws/chat-client-ui-types": "^0.1.24", - "@aws/language-server-runtimes": "^0.2.58", - "@aws/language-server-runtimes-types": "^0.1.13", + "@aws/language-server-runtimes": "^0.2.70", + "@aws/language-server-runtimes-types": "^0.1.21", "@cspotcode/source-map-support": "^0.8.1", "@sinonjs/fake-timers": "^10.0.2", "@types/adm-zip": "^0.4.34", From 61805b726dca17e2105ceb26930bd56e47eb638c Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:50:05 -0400 Subject: [PATCH 039/153] feat(amazonq): emit flare telemetry events (#7124) ## Problem telemetry events forwarded to flare aren't emitted ## Solution emit them --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index da84162f2ec..9a53610f761 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -58,6 +58,7 @@ import { AmazonQChatViewProvider } from './webviewProvider' import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' import { DefaultAmazonQAppInitContext, messageDispatcher, EditorContentController } from 'aws-core-vscode/amazonq' +import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -80,7 +81,11 @@ export function registerLanguageServerEventListener(languageClient: LanguageClie }) languageClient.onTelemetry((e) => { - languageClient.info(`[VSCode Client] Received telemetry event from server ${JSON.stringify(e)}`) + const telemetryName: string = e.name + + if (telemetryName in telemetry) { + telemetry[telemetryName as keyof TelemetryBase].emit(e.data) + } }) } From 67825099001f0f1ab9e1e5c394d9d2a0bb739527 Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Tue, 22 Apr 2025 10:14:45 -0700 Subject: [PATCH 040/153] fix(amazonq): diff not appearing for new files (#7127) ## Problem Clicking on diff for newly created files did not open diff ## Solution Allow opening diffs for empty string --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 2 +- .../core/src/amazonq/commons/controllers/contentController.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 9a53610f761..01a3624f195 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -452,7 +452,7 @@ export function registerMessageListeners( activeFileContext: { filePath: params.originalFileUri }, focusAreaContext: { selectionInsideExtendedCodeBlock: entireDocumentSelection }, }, - code: params.fileContent, + code: params.fileContent ?? '', }, amazonQDiffScheme, true diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index edb9ac7bd87..2586951a18e 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -161,7 +161,7 @@ export class EditorContentController { const { filePath, selection } = extractFileAndCodeSelectionFromMessage(message) try { - if (filePath && message?.code?.trim().length > 0 && selection) { + if (filePath && message?.code !== undefined && selection) { const originalFileUri = vscode.Uri.file(filePath) const uri = await createTempFileForDiff(originalFileUri, message, selection, scheme) From 009258c9b01aa6bf4302f2e1edc1ac4aa9e56734 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Tue, 22 Apr 2025 13:17:55 -0400 Subject: [PATCH 041/153] fix(amazonq): show empty response when token was cancelled (#7128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem in some cases when tools are cancelled on the flare side, a cancellation error will hit our catch and log "❌ Error: Request failed to complete" ## Solution when the token was canceled don't log anything --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 01a3624f195..c82d4c24570 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -254,11 +254,16 @@ export function registerMessageListeners( } catch (e) { languageClient.info(`Error occurred during chat request: ${e}`) // Use the last partial result if available, append error message + let body = '' + if (!cancellationToken.token.isCancellationRequested) { + body = lastPartialResult?.body + ? `${lastPartialResult.body}\n\n ❌ Error: Request failed to complete` + : '❌ An error occurred while processing your request' + } + const errorResult: ChatResult = { ...lastPartialResult, - body: lastPartialResult?.body - ? `${lastPartialResult.body}\n\n ❌ Error: Request failed to complete` - : '❌ An error occurred while processing your request', + body, } await handleCompleteResult( From 69f6d017dd936e6b9f6df86bc91c6c09bd77b730 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:18:09 -0400 Subject: [PATCH 042/153] fix(amazonq): add highlight feature config to context commands (#7131) ## Problem we fetch feature configs but we never send them to flare chat client ## Solution send them --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/webviewProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index 025881f3460..e53e3d7afd9 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -146,7 +146,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { const vscodeApi = acquireVsCodeApi() const hybridChatConnector = new HybridChatAdapter(${(await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected'},${featureConfigData},${welcomeCount},${disclaimerAcknowledged},${regionProfileString},${disabledCommands},${isSMUS},${isSM},vscodeApi.postMessage) const commands = [hybridChatConnector.initialQuickActions[0]] - amazonQChat.createChat(vscodeApi, {disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, quickActionCommands: commands}, hybridChatConnector); + amazonQChat.createChat(vscodeApi, {disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, quickActionCommands: commands}, hybridChatConnector, ${JSON.stringify(featureConfigData)}); } From 9d7f4452b7b20b5e340c7061467b2319c5de7390 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:21:25 -0700 Subject: [PATCH 043/153] fix(amazonq): revert 7060 show customization across profiles (#7129) Due to throttling issue we already encounter now in PROD, we decided to revert it back and fix the throttling issue first then bring this back afterward. This reverts commit 70ba83fc066cfe8a1f9f50e5eb73a5e1b51d42f4. ## Problem ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- ...-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json | 4 - .../region/regionProfileManager.test.ts | 56 +-------- packages/core/src/codewhisperer/activation.ts | 28 ++++- .../src/codewhisperer/client/codewhisperer.ts | 26 +++- packages/core/src/codewhisperer/index.ts | 8 +- .../region/regionProfileManager.ts | 38 ++---- .../codewhisperer/util/customizationUtil.ts | 111 ++---------------- packages/core/src/shared/featureConfig.ts | 27 +++-- .../test/amazonq/customizationUtil.test.ts | 41 ------- 9 files changed, 90 insertions(+), 249 deletions(-) delete mode 100644 packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json diff --git a/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json b/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json deleted file mode 100644 index d3838c1b4d6..00000000000 --- a/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "Support selecting customizations across all Q profiles with automatic profile switching for enterprise users" -} diff --git a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index 11441b9bf6f..af79f7dc2e5 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -65,7 +65,7 @@ describe('RegionProfileManager', function () { const mockClient = { listAvailableProfiles: listProfilesStub, } - const createClientStub = sinon.stub(sut, '_createQClient').resolves(mockClient) + const createClientStub = sinon.stub(sut, 'createQClient').resolves(mockClient) const r = await sut.listRegionProfile() @@ -234,65 +234,13 @@ describe('RegionProfileManager', function () { }) describe('createQClient', function () { - it(`should configure the endpoint and region from a profile`, async function () { - await setupConnection('idc') - - const iadClient = await sut.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 sut.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 sut.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 sut.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 sut.switchRegionProfile(profileFoo, 'user') assert.deepStrictEqual(sut.activeRegionProfile, profileFoo) const conn = authUtil.conn as SsoConnection - const client = await sut._createQClient('eu-central-1', 'https://amazon.com/', conn) + const client = await sut.createQClient('eu-central-1', 'https://amazon.com/', conn) assert.deepStrictEqual(client.config.region, 'eu-central-1') assert.deepStrictEqual(client.endpoint.href, 'https://amazon.com/') diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index 7a7f2b7b573..efebb01e179 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -72,7 +72,12 @@ import { AuthUtil } from './util/authUtil' import { ImportAdderProvider } from './service/importAdderProvider' import { TelemetryHelper } from './util/telemetryHelper' import { openUrl } from '../shared/utilities/vsCodeUtils' -import { notifyNewCustomizations, onProfileChangedListener } from './util/customizationUtil' +import { + getAvailableCustomizationsList, + getSelectedCustomization, + notifyNewCustomizations, + switchToBaseCustomizationAndNotify, +} from './util/customizationUtil' import { CodeWhispererCommandBackend, CodeWhispererCommandDeclarations } from './commands/gettingStartedPageCommands' import { SecurityIssueHoverProvider } from './service/securityIssueHoverProvider' import { SecurityIssueCodeActionProvider } from './service/securityIssueCodeActionProvider' @@ -338,7 +343,26 @@ export async function activate(context: ExtContext): Promise { SecurityIssueCodeActionProvider.instance ), vscode.commands.registerCommand('aws.amazonq.openEditorAtRange', openEditorAtRange), - auth.regionProfileManager.onDidChangeRegionProfile(onProfileChangedListener) + 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 + ) + }) + } + }) ) // run the auth startup code with context for telemetry diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 35f699b24c2..2412f6922a8 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -7,17 +7,19 @@ import { AWSError, Credentials, Service } from 'aws-sdk' import globals from '../../shared/extensionGlobals' import * as CodeWhispererClient from './codewhispererclient' import * as CodeWhispererUserClient from './codewhispereruserclient' -import { SendTelemetryEventRequest } from './codewhispereruserclient' +import { ListAvailableCustomizationsResponse, 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 { isSsoConnection } from '../../auth/connection' +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' @@ -217,6 +219,28 @@ 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/index.ts b/packages/core/src/codewhisperer/index.ts index 98b7f9239b1..930b168beec 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -99,13 +99,7 @@ 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, - onProfileChangedListener, - CustomizationProvider, -} from './util/customizationUtil' +export { getSelectedCustomization, setSelectedCustomization, baseCustomization } from './util/customizationUtil' export { Container } from './service/serviceContainer' export * from './util/gitUtil' export * from './ui/prompters' diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index 53c159efdb7..effb5e3a84b 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -47,17 +47,12 @@ 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' | 'customization' - -export type ProfileChangedEvent = { - profile: RegionProfile | undefined - intent: ProfileSwitchIntent -} +export type ProfileSwitchIntent = 'user' | 'auth' | 'update' | 'reload' 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 @@ -117,7 +112,7 @@ export class RegionProfileManager { } const availableProfiles: RegionProfile[] = [] for (const [region, endpoint] of endpoints.entries()) { - const client = await this._createQClient(region, endpoint, conn as SsoConnection) + const client = await this.createQClient(region, endpoint, conn as SsoConnection) const requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) => client.listAvailableProfiles(request).promise() const request: CodeWhispererUserClient.ListAvailableProfilesRequest = {} @@ -167,7 +162,7 @@ export class RegionProfileManager { const ssoConn = this.connectionProvider() as SsoConnection // only prompt to users when users switch from A profile to B profile - if (source !== 'customization' && this.activeRegionProfile !== undefined && regionProfile !== undefined) { + if (this.activeRegionProfile !== undefined && regionProfile !== undefined) { const response = await showConfirmationMessage({ prompt: localize( 'AWS.amazonq.profile.confirmation', @@ -209,16 +204,13 @@ export class RegionProfileManager { }) } - await this._switchRegionProfile(regionProfile, source) + await this._switchRegionProfile(regionProfile) } - private async _switchRegionProfile(regionProfile: RegionProfile | undefined, source: ProfileSwitchIntent) { + private async _switchRegionProfile(regionProfile: RegionProfile | undefined) { this._activeRegionProfile = regionProfile - this._onDidChangeRegionProfile.fire({ - profile: regionProfile, - intent: source, - }) + this._onDidChangeRegionProfile.fire(regionProfile) // 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() @@ -351,21 +343,7 @@ export class RegionProfileManager { } } - // TODO: Should maintain sdk client in a better way - async createQClient(profile: RegionProfile): Promise { - const conn = this.connectionProvider() - if (conn === undefined || !isSsoConnection(conn)) { - 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, conn) - } - - // Visible for testing only, do not use this directly, please use createQClient(profile) - async _createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { + async createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { const token = (await conn.getToken()).accessToken const serviceOption: ServiceOptions = { apiConfig: userApiConfig, diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index 50ace5379bd..7898b7a17ba 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -10,77 +10,14 @@ import { AuthUtil } from './authUtil' import * as vscode from 'vscode' import { createCommonButtons } from '../../shared/ui/buttons' import { DataQuickPickItem, showQuickPick } from '../../shared/ui/pickerPrompter' -import CodeWhispererUserClient, { Customization, ResourceArn } from '../client/codewhispereruserclient' +import { codeWhispererClient } from '../client/codewhisperer' +import { 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 { 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() - } - } -} +import { vsCodeState } from '../models/model' /** * @@ -287,6 +224,7 @@ 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', @@ -345,12 +283,8 @@ 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 & { profile: RegionProfile }, + customization: Customization, persistedArns: (ResourceArn | undefined)[], shouldPrefixAccountId: boolean ) => { @@ -359,8 +293,8 @@ const createCustomizationItem = ( ? shouldPrefixAccountId ? accountId ? `${customization.name} (${accountId})` - : `${customization.name} (${customization.profile.name})` - : `${customization.name} (${customization.profile.name})` + : `${customization.name}` + : customization.name : 'unknown' const isNewCustomization = !persistedArns.includes(customization.arn) @@ -369,10 +303,6 @@ 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: @@ -403,28 +333,13 @@ 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 & { profile: RegionProfile })[] = [] - const profiles: RegionProfile[] = [] - try { - const r = await AuthUtil.instance.regionProfileManager.listRegionProfile() - 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, - }) - } + const items: Customization[] = [] + const response = await codeWhispererClient.listAvailableCustomizations() + for (const customizations of response.map( + (listAvailableCustomizationsResponse) => listAvailableCustomizationsResponse.customizations + )) { + items.push(...customizations) } return items diff --git a/packages/core/src/shared/featureConfig.ts b/packages/core/src/shared/featureConfig.ts index d7acb9657be..6d59fe0782a 100644 --- a/packages/core/src/shared/featureConfig.ts +++ b/packages/core/src/shared/featureConfig.ts @@ -4,6 +4,7 @@ */ import { + Customization, FeatureValue, ListFeatureEvaluationsRequest, ListFeatureEvaluationsResponse, @@ -20,7 +21,7 @@ import { getClientId, getOperatingSystem } from './telemetry/util' import { extensionVersion } from './vscode/env' import { telemetry } from './telemetry/telemetry' import { Commands } from './vscode/commands2' -import { getAvailableCustomizationsList, setSelectedCustomization } from '../codewhisperer/util/customizationUtil' +import { setSelectedCustomization } from '../codewhisperer/util/customizationUtil' const localize = nls.loadMessageBundle() @@ -152,7 +153,19 @@ export class FeatureConfigProvider { if (isBuilderIdConnection(AuthUtil.instance.conn)) { this.featureConfigs.delete(Features.customizationArnOverride) } else if (isIdcSsoConnection(AuthUtil.instance.conn)) { - const availableCustomizations = await getAvailableCustomizationsList() + 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') + } // If customizationArn from A/B is not available in listAvailableCustomizations response, don't use this value const targetCustomization = availableCustomizations?.find((c) => c.arn === customizationArnOverride) @@ -163,16 +176,6 @@ 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/test/amazonq/customizationUtil.test.ts b/packages/core/src/test/amazonq/customizationUtil.test.ts index a3a49e907d9..505c89ae0c9 100644 --- a/packages/core/src/test/amazonq/customizationUtil.test.ts +++ b/packages/core/src/test/amazonq/customizationUtil.test.ts @@ -11,11 +11,9 @@ import { AuthUtil, baseCustomization, Customization, - CustomizationProvider, FeatureConfigProvider, getSelectedCustomization, refreshStatusBar, - RegionProfileManager, setSelectedCustomization, } from '../../codewhisperer' import { FeatureContext, globals } from '../../shared' @@ -25,45 +23,6 @@ import { SsoConnection } from '../../auth' const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' -describe('customizationProvider', function () { - let auth: ReturnType - let ssoConn: SsoConnection - let regionProfileManager: RegionProfileManager - - beforeEach(async () => { - auth = createTestAuth(globals.globalState) - ssoConn = await auth.createInvalidSsoConnection( - createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) - ) - - regionProfileManager = new RegionProfileManager(() => ssoConn) - }) - - 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 ssoConn: SsoConnection From 8da7910ffffaf9aaad0c8cd00bb9dca8ac2de278 Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Tue, 22 Apr 2025 13:26:10 -0700 Subject: [PATCH 044/153] fix(amazonq): forward chat update notification (#7136) ## Problem ChatUpdate notifications are not getting forwarded to UI ## Solution Forward chat update notification --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index c82d4c24570..3dddd12ab8c 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -49,6 +49,8 @@ import { buttonClickRequestType, ButtonClickResult, CancellationTokenSource, + chatUpdateNotificationType, + ChatUpdateParams, } from '@aws/language-server-runtimes/protocol' import { v4 as uuidv4 } from 'uuid' import * as vscode from 'vscode' @@ -463,6 +465,13 @@ export function registerMessageListeners( true ) }) + + languageClient.onNotification(chatUpdateNotificationType.method, (params: ChatUpdateParams) => { + void provider.webview?.postMessage({ + command: chatUpdateNotificationType.method, + params: params, + }) + }) } function isServerEvent(command: string) { From 96e28f4ce23ef73fe84b9be63b4fa35a618743ca Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Tue, 22 Apr 2025 18:06:19 -0400 Subject: [PATCH 045/153] fix(amazonq): indicate profile needs to be selected from status bar menu (#7137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem: Inline suggestion users who upgraded to the new extension version with region profile selection need to select a profile, but are not prompted in a significant way to do so. If they didn't select a profile, inline suggestions would just stop working for them. There is no obvious hint to the user that to get inline suggestions working again they must select a profile. ## Solution: If a profile is not selected, show the status bar as not connected. Once the status bar is clicked they will see the top item telling them they must select a profile to get features working. This opens a quick pick of the profiles they can select from. Screenshot 2025-04-22 at 3 29 54 PM https://github.com/user-attachments/assets/7115ed56-8faf-486f-beaa-6f09cbca56e7 --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Signed-off-by: nkomonen-amazon --- ... Fix-4323842a-bd34-4c49-9a03-12620cce0203.json | 4 ++++ .../codewhisperer/region/regionProfileManager.ts | 4 ++++ .../service/inlineCompletionService.ts | 9 +++++++++ .../src/codewhisperer/ui/codeWhispererNodes.ts | 15 ++++++++++----- .../core/src/codewhisperer/ui/statusBarMenu.ts | 9 ++++++++- 5 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json diff --git a/packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json b/packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json new file mode 100644 index 00000000000..7634c9bf8f1 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Some users not signaled they needed to select a Region Profile to get features working" +} diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index effb5e3a84b..039d5cefb59 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -28,6 +28,7 @@ import { parse } from '@aws-sdk/util-arn-parser' import { isAwsError, ToolkitError } from '../../shared/errors' import { telemetry } from '../../shared/telemetry/telemetry' import { localize } from '../../shared/utilities/vsCodeUtils' +import { Commands } from '../../shared/vscode/commands2' // TODO: is there a better way to manage all endpoint strings in one place? export const defaultServiceConfig: CodeWhispererConfig = { @@ -218,6 +219,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 () => { diff --git a/packages/core/src/codewhisperer/service/inlineCompletionService.ts b/packages/core/src/codewhisperer/service/inlineCompletionService.ts index 715fd93ad2d..cc9887adb1f 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.isConnectionValid()) { + if (AuthUtil.instance.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/ui/codeWhispererNodes.ts b/packages/core/src/codewhisperer/ui/codeWhispererNodes.ts index d804cbedd46..c3e46bdc78e 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' @@ -138,12 +138,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', @@ -152,6 +156,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 9c7ea8c43ec..2ad14a81df0 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.isValidEnterpriseSsoInUse() + 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.isValidEnterpriseSsoInUse() ? [createSelectRegionProfileNode()] : []), + ...(isUsingEnterpriseSso && regionProfile ? [createSelectRegionProfileNode(regionProfile)] : []), ...(AuthUtil.instance.isConnected() && !hasVendedIamCredentials() && !hasVendedCredentialsFromMetadata() ? [createSignout()] : []), From 826f84d3a2ff008a72732f8b8a58ad6d378ec0a3 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Tue, 22 Apr 2025 22:10:46 +0000 Subject: [PATCH 046/153] Release 1.61.0 --- package-lock.json | 4 ++-- packages/amazonq/.changes/1.61.0.json | 14 ++++++++++++++ ...g Fix-4323842a-bd34-4c49-9a03-12620cce0203.json | 4 ---- ...ugfix-ad9a8829-efa0-463b-ba2f-c89ff1cb69e4.json | 4 ---- packages/amazonq/CHANGELOG.md | 5 +++++ packages/amazonq/package.json | 2 +- 6 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 packages/amazonq/.changes/1.61.0.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json delete mode 100644 packages/amazonq/.changes/next-release/bugfix-ad9a8829-efa0-463b-ba2f-c89ff1cb69e4.json diff --git a/package-lock.json b/package-lock.json index 35e15539e63..3be1ae0a2ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26694,7 +26694,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.61.0-SNAPSHOT", + "version": "1.61.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json b/packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json deleted file mode 100644 index 7634c9bf8f1..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-4323842a-bd34-4c49-9a03-12620cce0203.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Some users not signaled they needed to select a Region Profile to get features working" -} diff --git a/packages/amazonq/.changes/next-release/bugfix-ad9a8829-efa0-463b-ba2f-c89ff1cb69e4.json b/packages/amazonq/.changes/next-release/bugfix-ad9a8829-efa0-463b-ba2f-c89ff1cb69e4.json deleted file mode 100644 index 1b84a3f57c0..00000000000 --- a/packages/amazonq/.changes/next-release/bugfix-ad9a8829-efa0-463b-ba2f-c89ff1cb69e4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "bugfix", - "description": "/review: disable auto-review by default" -} diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index 64753856884..3f1fedb6305 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,8 @@ +## 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..e6f68c7dc95 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.61.0", "extensionKind": [ "workspace" ], From c22efa03e73b241564c8051c35761eb8620edb83 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Tue, 22 Apr 2025 23:15:03 +0000 Subject: [PATCH 047/153] Update version to snapshot version: 1.62.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/amazonq/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3be1ae0a2ec..9afa7c62511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26694,7 +26694,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.61.0", + "version": "1.62.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index e6f68c7dc95..20fdc3610ef 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", + "version": "1.62.0-SNAPSHOT", "extensionKind": [ "workspace" ], From cda61ec1e390b454ac65d179c0a2737088b33174 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:27:02 -0400 Subject: [PATCH 048/153] fix(amazonq): simulate refresh of chat (#7142) ## Problem Reloading the webview directly causes export/history to be missing ## Solution Add a specialized handler that simulates reloading the webview when a profile changes, rather than actually refresh everything. This is required because the chat-client relies on initializedResult values from the language server that are only sent once --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../amazonq/src/lsp/chat/webviewProvider.ts | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index e53e3d7afd9..d47563169d7 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -80,12 +80,6 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { this.webview = this.webviewView.webview this.onDidResolveWebviewEmitter.fire() - globals.context.subscriptions.push( - this.webviewView.onDidDispose(() => { - this.webviewView = undefined - this.webview = undefined - }) - ) performance.mark(amazonqMark.open) } @@ -142,12 +136,31 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { ` @@ -155,8 +168,10 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { async refreshWebview() { if (this.webview) { - // refresh the webview when the profile changes - this.webview.html = await this.getWebviewContent() + // post a message to the webview telling it to reload + void this.webview?.postMessage({ + command: 'reload', + }) } } } From 7291993d998e3817c6fd98cf697e087e2b251342 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:11:30 -0400 Subject: [PATCH 049/153] Revert "fix(amazonq): temporarily disable q developer profiles" (#7138) Reverts aws/aws-toolkit-vscode#7118 We were getting throttled because of https://github.com/aws/aws-toolkit-vscode/pull/7060. That's been reverted so we should be safe to re-enable this --- packages/amazonq/src/lsp/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 7e612102c23..b3205d009d6 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -127,7 +127,7 @@ export async function startLanguageServer( }, awsClientCapabilities: { q: { - developerProfiles: false, + developerProfiles: true, }, window: { notifications: true, From 7cf7897ea9317aebbae6d54004e488a316b36dfb Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:12:13 -0400 Subject: [PATCH 050/153] fix(amazonq): deny restoreTabMessage/contextCommandData messages from cwc chat (#7144) ## Problem cw chat is still registered and sending events to the frontend, in some cases those UI handlers are the same as they are on the chat-client. This causes disruptive behavior to happen like "normal" chat tabs being restored when they shouldn't ## Solution disable restoreTabMessage, contextCommandData ## TODO get rid of cw app start up --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/activation.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index 3592c66fed4..33795219bff 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -45,6 +45,14 @@ export async function activate(languageClient: LanguageClient, encryptionKey: Bu 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) }) From 1b7a86cdea1b5798e1813a85d76264aaf4597944 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Wed, 23 Apr 2025 15:47:24 -0400 Subject: [PATCH 051/153] fix(amazonq): Add clarifying comment to telemetry pass through mechanism (#7146) ## Problem It's not obvious that the LSP language client passes on telemetry events to Toolkits Telemetry ## Solution Add clarifying comment to the code implementation --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 3dddd12ab8c..6cdf3bbd3c5 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -82,6 +82,7 @@ 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) => { const telemetryName: string = e.name From 4ba52513d24def9429728a5d12ada57c3ca3666b Mon Sep 17 00:00:00 2001 From: Josh Pinkney Date: Wed, 23 Apr 2025 15:51:53 -0400 Subject: [PATCH 052/153] fix(amazonq): developer profile api requests are still throttling --- packages/amazonq/src/lsp/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index b3205d009d6..7e612102c23 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -127,7 +127,7 @@ export async function startLanguageServer( }, awsClientCapabilities: { q: { - developerProfiles: true, + developerProfiles: false, }, window: { notifications: true, From 0bd3e12e7159c7a5fd338d3826152c793526c84c Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:27:15 -0400 Subject: [PATCH 053/153] fix(lsp): LSP download timeout message disappears too early (#7145) ## Problem: When downloading the LSP artifacts from the manifest, there are some large ones (100+ MB). For a slow connection this can take 1+ minutes, but our timeout for the "downloading" message is set to disappear much earlier. So it is still downloading in the background, but the message has disappeared and the user thinks that the download is done. ## Solution: Increase the timeout to 30 minutes, which will ensure the downloading message sticks around while the download is still happening. ## Additional In another commit this fixes a separate HTTP client bug where it would time out a request if it took longer than 3 seconds. This caused downloads to be aborted (separate from the download message disappearing). Now it times out after 30 minutes. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/core/src/shared/lsp/lspResolver.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/shared/lsp/lspResolver.ts b/packages/core/src/shared/lsp/lspResolver.ts index 90892148a9f..70f66cafd14 100644 --- a/packages/core/src/shared/lsp/lspResolver.ts +++ b/packages/core/src/shared/lsp/lspResolver.ts @@ -15,9 +15,11 @@ import { lspSetupStage, StageResolver, tryStageResolvers } from './utils/setupSt import { HttpResourceFetcher } from '../resourcefetcher/httpResourceFetcher' import { showMessageWithCancel } from '../../shared/utilities/messages' import { Timeout } from '../utilities/timeoutUtils' +import { oneMinute } from '../datetime' -// 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 { constructor( From e39a32bab356066e9e7129b35e3892d05ee4bb66 Mon Sep 17 00:00:00 2001 From: chungjac Date: Wed, 23 Apr 2025 13:33:07 -0700 Subject: [PATCH 054/153] telemetry(amazonq): bump aws-toolkit-common version to 1.0.316 (#7147) ## Problem toolkit commons needs to be updated so that telemetry events from flare get sent to kibana ## Solution - bump aws-toolkit-common version from 1.0.312 to 1.0.316 --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- package-lock.json | 6 ++++-- package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c99210de0f2..87fd273ac31 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.316", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", @@ -10760,7 +10760,9 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.312", + "version": "1.0.316", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.316.tgz", + "integrity": "sha512-zyzubs7fnCLPqdNtfHdmmNXVWrwIWJHIr6LJqdvUtNfdGmN8PWxq/NdBgCP9QcPVZusuK7SHha1knl8vhped/w==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 637fc21649a..67c648d81db 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.316", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", From c9eb33452a9524703292cce28cbd71e6bac122ef Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:36:01 -0400 Subject: [PATCH 055/153] fix(amazonq): send sso startUrl on token update (#7148) ## Problem race conditions can occur with getConnectionMetadata ## Solution instead send the start url through the token update. When it's done this way you don't need to [set getConnectionMetadata](https://github.com/aws/language-server-runtimes/blob/5ba754af403a6f35cd771f27efb987c1580ae6b5/runtimes/runtimes/auth/auth.ts#L158) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/auth.ts | 11 +++++++++-- packages/amazonq/src/lsp/client.ts | 9 --------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/amazonq/src/lsp/auth.ts b/packages/amazonq/src/lsp/auth.ts index 4eb92e40788..ed5753e9d82 100644 --- a/packages/amazonq/src/lsp/auth.ts +++ b/packages/amazonq/src/lsp/auth.ts @@ -4,10 +4,12 @@ */ import { + bearerCredentialsUpdateRequestType, ConnectionMetadata, NotificationType, RequestType, ResponseMessage, + UpdateCredentialsParams, } from '@aws/language-server-runtimes/protocol' import * as jose from 'jose' import * as crypto from 'crypto' @@ -81,7 +83,7 @@ export class AmazonQLspAuth { token, }) - await this.client.sendRequest(notificationTypes.updateBearerToken.method, request) + await this.client.sendRequest(bearerCredentialsUpdateRequestType.method, request) this.client.info(`UpdateBearerToken: ${JSON.stringify(request)}`) } @@ -96,7 +98,7 @@ export class AmazonQLspAuth { return interval } - private async createUpdateCredentialsRequest(data: any) { + private async createUpdateCredentialsRequest(data: any): Promise { const payload = new TextEncoder().encode(JSON.stringify({ data })) const jwt = await new jose.CompactEncrypt(payload) @@ -105,6 +107,11 @@ export class AmazonQLspAuth { return { data: jwt, + metadata: { + sso: { + startUrl: AuthUtil.instance.auth.startUrl, + }, + }, encrypted: true, } } diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 7e612102c23..fa45e269114 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -10,7 +10,6 @@ import { LanguageClient, LanguageClientOptions, RequestType } from 'vscode-langu import { InlineCompletionManager } from '../app/inline/completion' import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth' import { - ConnectionMetadata, CreateFilesParams, DeleteFilesParams, DidChangeWorkspaceFoldersParams, @@ -164,14 +163,6 @@ export async function startLanguageServer( const auth = new AmazonQLspAuth(client) return client.onReady().then(async () => { - // 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(notificationTypes.getConnectionMetadata.method, () => { - return { - sso: { - startUrl: AuthUtil.instance.auth.startUrl, - }, - } - }) await auth.refreshConnection() if (Experiments.instance.get('amazonqLSPInline', false)) { From dc849e70dc6b93c82945738d4dce4c599749b59c Mon Sep 17 00:00:00 2001 From: Dogus Atasoy <116281103+dogusata@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:41:25 +0200 Subject: [PATCH 056/153] feat(amazonq): update mynah version with no style loading option (#7154) ## Problem - UI styles coming from flare and the local instance are conflictin. ## Solution - Latest production version of mynah-ui has the ability to avoid loading styles. Bumped up mynah-ui version and set `loadStyles` to `false`. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- package-lock.json | 6 ++++-- packages/core/package.json | 2 +- packages/core/src/amazonq/webview/ui/main.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87fd273ac31..9c2ed110884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11781,7 +11781,9 @@ } }, "node_modules/@aws/mynah-ui": { - "version": "4.30.1", + "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": { @@ -26424,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", diff --git a/packages/core/package.json b/packages/core/package.json index 5d60b72cc58..31d928da9fa 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -526,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/src/amazonq/webview/ui/main.ts b/packages/core/src/amazonq/webview/ui/main.ts index 81b6fdb1941..7d5bd48eaeb 100644 --- a/packages/core/src/amazonq/webview/ui/main.ts +++ b/packages/core/src/amazonq/webview/ui/main.ts @@ -1017,7 +1017,7 @@ export class WebviewUIHandler { * 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) } + this.mynahUIRef = { mynahUI: new MynahUI({ ...this.mynahUIProps, loadStyles: false }) } } /** From d2f6b9a9c88c18e4deda35b7b89ebcda3d6067d4 Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Thu, 24 Apr 2025 12:35:48 -0400 Subject: [PATCH 057/153] fix(chat): Resend auth token on server restart (#7156) ## Problem: When the server restarted due to a crash, the auth token was not sent again. This caused users to run in to the state where if it crashed and restarted, when they sent a subsequent chat message they'd get stuck with the server asking the user to Authenticate. Note that server restart is triggered automatically by the LanguageClient, I think. ## Solution: Detect when the server is restarted and manually resend the bearer token again. Note, this solution needs to be revisited since there may be other initialization logic that needs to run on server restart, aside from just the bearer token. ## Repro Steps: 1. Ensure you do not have a workspace open, you can open a new vscode window at the top left `File` > `New Window` 2. Make a random folder in your home directory 3. Make a couple typescript files in that folder 4. Send the prompt: `list all files in {folder}` 5. Accept the permissions 6. Click the toggle drop down from the response, and click the link to the path (this is just to force a crash + restart) 7. Verify the server crashed in the logs, look for the message `Connection to server got closed. Server will restart.` 8. ASSUMING this fix worked, the server will restart automatically and you can continue using chat. Before this fix any subsequent messages would ask the user to `Authenticate` again --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/amazonq/src/lsp/auth.ts | 7 +++++-- packages/amazonq/src/lsp/client.ts | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/amazonq/src/lsp/auth.ts b/packages/amazonq/src/lsp/auth.ts index ed5753e9d82..6a0953c98ab 100644 --- a/packages/amazonq/src/lsp/auth.ts +++ b/packages/amazonq/src/lsp/auth.ts @@ -68,12 +68,15 @@ export const notificationTypes = { export class AmazonQLspAuth { constructor(private readonly client: LanguageClient) {} - async refreshConnection() { + /** + * @param force bypass memoization, and forcefully update the bearer token + */ + async refreshConnection(force: boolean = false) { const activeConnection = AuthUtil.instance.auth.activeConnection if (activeConnection?.type === 'sso') { // send the token to the language server const token = await AuthUtil.instance.getBearerToken() - await this.updateBearerToken(token) + await (force ? this._updateBearerToken(token) : this.updateBearerToken(token)) } } diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index fa45e269114..3bc5622d837 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -6,7 +6,7 @@ import vscode, { env, version } from 'vscode' import * as nls from 'vscode-nls' import * as crypto from 'crypto' -import { LanguageClient, LanguageClientOptions, RequestType } from 'vscode-languageclient' +import { LanguageClient, LanguageClientOptions, RequestType, State } from 'vscode-languageclient' import { InlineCompletionManager } from '../app/inline/completion' import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth' import { @@ -268,7 +268,25 @@ export async function startLanguageServer( }, } as DidChangeWorkspaceFoldersParams) }), - { dispose: () => clearInterval(refreshInterval) } + { dispose: () => clearInterval(refreshInterval) }, + // Set this inside onReady so that it only triggers on subsequent language server starts (not the first) + onServerRestartHandler(client, auth) ) }) } + +/** + * 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, auth: AmazonQLspAuth) { + return client.onDidChangeState(async (e) => { + // Ensure we are in a "restart" state + if (!(e.oldState === State.Starting && e.newState === State.Running)) { + return + } + + // Need to set the auth token in the again + await auth.refreshConnection(true) + }) +} From 42636aa6206eef851e3b81d745c37718a709700e Mon Sep 17 00:00:00 2001 From: chungjac Date: Thu, 24 Apr 2025 10:04:10 -0700 Subject: [PATCH 058/153] telemetry(amazonq): bump aws-toolkit-common #7157 ## Problem - toolkit commons needs to be updated so that certain telemetry events from flare have `languageServerVersion` ## Solution - bump aws-toolkit-common version from 1.0.316 to 1.0.317 - remove `amazonq_messageResponseError` because it was uplifted to common: https://github.com/aws/aws-toolkit-common/pull/1019 --- package-lock.json | 8 +-- package.json | 2 +- .../src/shared/telemetry/vscodeTelemetry.json | 55 ------------------- 3 files changed, 5 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c2ed110884..8ddd858085d 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.316", + "@aws-toolkits/telemetry": "^1.0.317", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", @@ -10760,9 +10760,9 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.316", - "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.316.tgz", - "integrity": "sha512-zyzubs7fnCLPqdNtfHdmmNXVWrwIWJHIr6LJqdvUtNfdGmN8PWxq/NdBgCP9QcPVZusuK7SHha1knl8vhped/w==", + "version": "1.0.317", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.317.tgz", + "integrity": "sha512-QFLBFfHZjuB2pBd1p0Tn/GMKTYYQu3/nrlj0Co7EkqozvDNDG0nTjxtkXxotbwjrqVD5Sv8i46gEdgsyQ7at3w==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 67c648d81db..30f0497cdb2 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.316", + "@aws-toolkits/telemetry": "^1.0.317", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index ffd715cbed8..4a5117ee252 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", From d856334fe347e1b531622ff4943c71f63d045178 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 24 Apr 2025 16:06:00 -0400 Subject: [PATCH 059/153] feat(amazonq): Add project context to LSP client --- packages/amazonq/src/lsp/client.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 3bc5622d837..65059f71e55 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -79,6 +79,7 @@ export async function startLanguageServer( * Convert VSCode settings format to be compatible with flare's configs */ configuration: async (params, token, next) => { + const vscodeConfig = vscode.workspace.getConfiguration() const config = await next(params, token) if (params.items[0].section === 'aws.q') { const customization = undefinedIfEmpty(getSelectedCustomization().arn) @@ -92,7 +93,9 @@ export async function startLanguageServer( customization, optOutTelemetry: getOptOutPreference() === 'OPTOUT', projectContext: { - enableLocalIndexing: true, + enableLocalIndexing: vscodeConfig.get('amazonQ.workspaceIndex'), + enableGpuAcceleration: vscodeConfig.get('amazonQ.workspaceIndexUseGPU'), + indexWorkerThreads: vscodeConfig.get('amazonQ.workspaceIndexWorkerThreads'), }, }, ] @@ -100,12 +103,10 @@ export async function startLanguageServer( if (params.items[0].section === 'aws.codeWhisperer') { return [ { - includeSuggestionsWithCodeReferences: vscode.workspace - .getConfiguration() - .get('amazonQ.showCodeWithReferences'), - shareCodeWhispererContentWithAWS: vscode.workspace - .getConfiguration() - .get('amazonQ.shareContentWithAWS'), + includeSuggestionsWithCodeReferences: vscodeConfig.get( + 'amazonQ.showCodeWithReferences' + ), + shareCodeWhispererContentWithAWS: vscodeConfig.get('amazonQ.shareContentWithAWS'), }, ] } From eb6c9c95817be8480ebdb239d737a74d93ef4a16 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:11:50 -0400 Subject: [PATCH 060/153] fix(amazonq): accept empty body partial result. (#7158) ## Problem If the server sends a partial result without a body, we currently ignore it. ## Solution - explicitly check for undefined. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 6cdf3bbd3c5..f896692606e 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -516,7 +516,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, From 879584ee283265f28002e7acc568c23d8b5c60d3 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 24 Apr 2025 16:20:39 -0400 Subject: [PATCH 061/153] Revert "feat(amazonq): Add project context to LSP client" This reverts commit d856334fe347e1b531622ff4943c71f63d045178. --- packages/amazonq/src/lsp/client.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 65059f71e55..3bc5622d837 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -79,7 +79,6 @@ export async function startLanguageServer( * Convert VSCode settings format to be compatible with flare's configs */ configuration: async (params, token, next) => { - const vscodeConfig = vscode.workspace.getConfiguration() const config = await next(params, token) if (params.items[0].section === 'aws.q') { const customization = undefinedIfEmpty(getSelectedCustomization().arn) @@ -93,9 +92,7 @@ export async function startLanguageServer( customization, optOutTelemetry: getOptOutPreference() === 'OPTOUT', projectContext: { - enableLocalIndexing: vscodeConfig.get('amazonQ.workspaceIndex'), - enableGpuAcceleration: vscodeConfig.get('amazonQ.workspaceIndexUseGPU'), - indexWorkerThreads: vscodeConfig.get('amazonQ.workspaceIndexWorkerThreads'), + enableLocalIndexing: true, }, }, ] @@ -103,10 +100,12 @@ export async function startLanguageServer( if (params.items[0].section === 'aws.codeWhisperer') { return [ { - includeSuggestionsWithCodeReferences: vscodeConfig.get( - 'amazonQ.showCodeWithReferences' - ), - shareCodeWhispererContentWithAWS: vscodeConfig.get('amazonQ.shareContentWithAWS'), + includeSuggestionsWithCodeReferences: vscode.workspace + .getConfiguration() + .get('amazonQ.showCodeWithReferences'), + shareCodeWhispererContentWithAWS: vscode.workspace + .getConfiguration() + .get('amazonQ.shareContentWithAWS'), }, ] } From dd9ad62bdcef4de467eb21b05034a127ddbecc91 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Fri, 25 Apr 2025 07:34:09 -0400 Subject: [PATCH 062/153] feat(amazonq): Pass project configuration to LSP client (#7162) ## Problem The LSP server accepts local VSCode workspace configurations for AmazonQ upon initialization, but they are not being passed to the client by the VSCode extension. See https://github.com/aws/language-servers/blob/main/server/aws-lsp-codewhisperer/src/shared/amazonQServiceManager/configurationUtils.ts ## Solution * Add missing settings for file indexing to the extension * Pass the workspace settings to Flare when the client is instantiated ## Testing Screenshot 2025-04-24 at 19 00 59 I confirmed that Flare is receiving the data and that it can parse it --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/package.json | 20 +++++++++++++++++- packages/amazonq/src/lsp/client.ts | 21 ++++++++++++------- packages/core/package.nls.json | 5 ++++- .../util/codewhispererSettings.ts | 18 +++++++++++++++- .../core/src/shared/settings-amazonq.gen.ts | 3 +++ 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index d9e1d35d624..1d285913708 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -181,7 +181,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/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 3bc5622d837..1373c026a9c 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -20,7 +20,7 @@ import { updateConfigurationRequestType, WorkspaceFolder, } from '@aws/language-server-runtimes/protocol' -import { AuthUtil, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' +import { AuthUtil, CodeWhispererSettings, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' import { Settings, oidcClientName, @@ -92,7 +92,15 @@ export async function startLanguageServer( customization, optOutTelemetry: getOptOutPreference() === 'OPTOUT', projectContext: { - enableLocalIndexing: true, + 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(), + }, }, }, ] @@ -100,12 +108,9 @@ export async function startLanguageServer( if (params.items[0].section === 'aws.codeWhisperer') { return [ { - includeSuggestionsWithCodeReferences: vscode.workspace - .getConfiguration() - .get('amazonQ.showCodeWithReferences'), - shareCodeWhispererContentWithAWS: vscode.workspace - .getConfiguration() - .get('amazonQ.shareContentWithAWS'), + includeSuggestionsWithCodeReferences: + CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled(), + shareCodeWhispererContentWithAWS: !CodeWhispererSettings.instance.isOptoutEnabled(), }, ] } 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/codewhisperer/util/codewhispererSettings.ts b/packages/core/src/codewhisperer/util/codewhispererSettings.ts index c63de7aa84f..4c30c0df467 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', undefined) + } + + public getIndexIgnoreFilePatterns(): string[] { + return this.get('workspaceIndexIgnoreFilePatterns', []) } public getAutoBuildSetting(): { [key: string]: boolean } { diff --git a/packages/core/src/shared/settings-amazonq.gen.ts b/packages/core/src/shared/settings-amazonq.gen.ts index 7bd20bc1e78..88324e10475 100644 --- a/packages/core/src/shared/settings-amazonq.gen.ts +++ b/packages/core/src/shared/settings-amazonq.gen.ts @@ -32,6 +32,9 @@ export const amazonqSettings = { "amazonQ.workspaceIndexWorkerThreads": {}, "amazonQ.workspaceIndexUseGPU": {}, "amazonQ.workspaceIndexMaxSize": {}, + "amazonQ.workspaceIndexMaxFileSize": {}, + "amazonQ.workspaceIndexCacheDirPath": {}, + "amazonQ.workspaceIndexIgnoreFilePatterns": {}, "amazonQ.ignoredSecurityIssues": {} } From 1e45112c8bb6c0c63fa271095fa24d9ce744a1d7 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:04:39 -0400 Subject: [PATCH 063/153] fix(amazonq): handle response errors from lsp (#7161) ## Problem Response errors returned by the LSP are returned to chat after prepending a custom error message. We can remove this custom error message and rely on flare for a single implementation of this error message. ## Solution - perform a sanity check on the response before casting it to the proper type. - log the last result from the language server to help debug. - include the requestId clearly in the logs. image --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/error.ts | 23 +++++++++++++++++++ packages/amazonq/src/lsp/chat/messages.ts | 23 ++++++++----------- .../test/unit/amazonq/lsp/chat/error.test.ts | 15 ++++++++++++ 3 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 packages/amazonq/src/lsp/chat/error.ts create mode 100644 packages/amazonq/test/unit/amazonq/lsp/chat/error.test.ts 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 f896692606e..0427a8610a3 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -61,6 +61,7 @@ import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' import { DefaultAmazonQAppInitContext, messageDispatcher, EditorContentController } 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( @@ -255,22 +256,16 @@ export function registerMessageListeners( chatDisposable ) } catch (e) { - languageClient.info(`Error occurred during chat request: ${e}`) - // Use the last partial result if available, append error message - let body = '' - if (!cancellationToken.token.isCancellationRequested) { - body = lastPartialResult?.body - ? `${lastPartialResult.body}\n\n ❌ Error: Request failed to complete` - : '❌ An error occurred while processing your request' - } - - const errorResult: ChatResult = { - ...lastPartialResult, - body, + 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( - errorResult, + e.data, encryptionKey, provider, chatParams.tabId, 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'))) + }) +}) From 8ece808e38b839e27df4ca76efecca86ed7561c0 Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:14:49 -0400 Subject: [PATCH 064/153] fix(amazonq): Warn user Developer Profile not selected (#7160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem: If a user has not selected Q Developer Profile after signing in, their features will not work. Some existing users, before the introduction of Q Developer Profiles, who were already signed in had their features stop working because they didn't select a profile. ## Solution: On startup if we detect the user did not select a profile, then send a warning message that their features will not work until they select one. The message will have a button to allow them to select a profile through quickpick, or entirly ignore the message and not show it again. Screenshot 2025-04-24 at 5 42 51 PM --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Signed-off-by: nkomonen-amazon --- ...-489e72aa-bb0d-4964-bd84-002f25db6b5f.json | 4 ++ packages/amazonq/package.json | 4 ++ packages/core/src/codewhisperer/activation.ts | 5 ++ .../core/src/codewhisperer/commands/types.ts | 3 ++ .../core/src/codewhisperer/region/utils.ts | 49 +++++++++++++++++++ .../core/src/codewhisperer/util/authUtil.ts | 1 + .../core/src/shared/settings-amazonq.gen.ts | 3 +- 7 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json create mode 100644 packages/core/src/codewhisperer/region/utils.ts diff --git a/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json b/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json new file mode 100644 index 00000000000..0cd71188f81 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Toast message to warn users if Developer Profile is not selected" +} diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 20fdc3610ef..cb74db00761 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -131,6 +131,10 @@ "amazonQChatDisclaimer": { "type": "boolean", "default": false + }, + "amazonQSelectDeveloperProfile": { + "type": "boolean", + "default": false } }, "additionalProperties": false diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index efebb01e179..b0aa54e17a0 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -95,6 +95,7 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr import { setContext } from '../shared/vscode/setContext' import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' import { detectCommentAboveLine } from '../shared/utilities/commentUtils' +import { notifySelectDeveloperProfile } from './region/utils' let localize: nls.LocalizeFunc @@ -380,6 +381,10 @@ export async function activate(context: ExtContext): Promise { await auth.notifySessionConfiguration() } } + + if (auth.requireProfileSelection()) { + await notifySelectDeveloperProfile() + } }, { emit: false, functionId: { name: 'activateCwCore' } } ) 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/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/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index 0898493b6db..f4cc90a5293 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -46,6 +46,7 @@ import { withTelemetryContext } from '../../shared/telemetry/util' import { focusAmazonQPanel } from '../../codewhispererChat/commands/registerCommands' import { throttle } from 'lodash' import { RegionProfileManager } from '../region/regionProfileManager' + /** Backwards compatibility for connections w pre-chat scopes */ export const codeWhispererCoreScopes = [...scopesCodeWhispererCore] export const codeWhispererChatScopes = [...codeWhispererCoreScopes, ...scopesCodeWhispererChat] diff --git a/packages/core/src/shared/settings-amazonq.gen.ts b/packages/core/src/shared/settings-amazonq.gen.ts index f0a3d47f989..bee57f9aa82 100644 --- a/packages/core/src/shared/settings-amazonq.gen.ts +++ b/packages/core/src/shared/settings-amazonq.gen.ts @@ -21,7 +21,8 @@ export const amazonqSettings = { "ssoCacheError": {}, "amazonQLspManifestMessage": {}, "amazonQWorkspaceLspManifestMessage": {}, - "amazonQChatDisclaimer": {} + "amazonQChatDisclaimer": {}, + "amazonQSelectDeveloperProfile": {} }, "amazonQ.showCodeWithReferences": {}, "amazonQ.allowFeatureDevelopmentToRunCodeAndTests": {}, From 638778beee1a88783753a93c70766c2ee55b789d Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:48:47 -0400 Subject: [PATCH 065/153] feat(core): add value length cap to partialClone (#7150) ## Problem If we want to log a large json object with giant strings (think whole files), it can make the logs difficult to read. This is especially relevant when the underlying string values are not very important. ## Solution - allow the string values to be truncated with `maxStringLength` option. ## Notes I am planning to port this utility to Flare to improve the logging experience there, and want this functionality to exist there however I thought this could be useful here too. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../amazonq/test/e2e/inline/inline.test.ts | 2 +- packages/core/src/auth/sso/clients.ts | 4 +- .../src/shared/utilities/collectionUtils.ts | 21 ++++- .../src/shared/utilities/textUtilities.ts | 2 +- packages/core/src/shared/vscode/commands2.ts | 2 +- .../shared/utilities/collectionUtils.test.ts | 91 ++++++++++++------- 6 files changed, 80 insertions(+), 42 deletions(-) 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/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/shared/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index 9f9fe9875b9..8a428b8e8b7 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) } } diff --git a/packages/core/src/shared/utilities/textUtilities.ts b/packages/core/src/shared/utilities/textUtilities.ts index 53c2e2be32c..ed1e1619122 100644 --- a/packages/core/src/shared/utilities/textUtilities.ts +++ b/packages/core/src/shared/utilities/textUtilities.ts @@ -10,7 +10,7 @@ import { default as stripAnsi } from 'strip-ansi' import { getLogger } from '../logger/logger' /** - * 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. * 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/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...' }], }) }) }) From 6db5eda9a7db3b7c9187755d204be9f687469440 Mon Sep 17 00:00:00 2001 From: Avi Alpert <131792194+avi-alpert@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:54:31 -0400 Subject: [PATCH 066/153] fix(amazonq): 401 errors on windows (#7168) ## Problem 401 and CSP errors when launching hybrid chat on windows ## Solution fix paths --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../amazonq/src/lsp/chat/webviewProvider.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index d47563169d7..6fac5a4afcf 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -42,7 +42,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { context: WebviewViewResolveContext, _token: CancellationToken ) { - const lspDir = Uri.parse(LanguageServerResolver.defaultDir()) + const lspDir = Uri.file(LanguageServerResolver.defaultDir()) const dist = Uri.joinPath(globals.context.extensionUri, 'dist') const resourcesRoots = [lspDir, dist] @@ -54,7 +54,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { const mynahUIPath = getAmazonQLspConfig().ui if (process.env.WEBPACK_DEVELOPER_SERVER && mynahUIPath) { const dir = path.dirname(mynahUIPath) - resourcesRoots.push(Uri.parse(dir)) + resourcesRoots.push(Uri.file(dir)) } webviewView.webview.options = { @@ -68,11 +68,9 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { this.connectorAdapterPath = serverHostname !== undefined - ? Uri.parse(serverHostname) - .with({ path: `/${source}` }) - .toString() - : webviewView.webview.asWebviewUri(Uri.parse(path.join(dist.fsPath, source))).toString() - this.uiPath = webviewView.webview.asWebviewUri(Uri.parse(this.mynahUIPath)).toString() + ? `${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() @@ -105,11 +103,11 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { const regionProfileString: string = JSON.stringify(regionProfile) const entrypoint = process.env.WEBPACK_DEVELOPER_SERVER - ? 'http: localhost' - : 'https: file+.vscode-resources.vscode-cdn.net' + ? 'http://localhost:8080' + : 'https://file+.vscode-resource.vscode-cdn.net' const contentPolicy = `default-src ${entrypoint} data: blob: 'unsafe-inline'; - script-src ${entrypoint} filesystem: ws: wss: 'unsafe-inline';` + script-src ${entrypoint} filesystem: file: vscode-resource: https: ws: wss: 'unsafe-inline';` return ` From be976e1176194acc8466fea6ef5ba06dc8de2d0e Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <43144436+aws-toolkit-automation@users.noreply.github.com> Date: Fri, 25 Apr 2025 14:58:00 -0400 Subject: [PATCH 067/153] Merge master into feature/hybridChat (#7166) ## Automatic merge failed - Resolve conflicts and push to this PR branch. - **Do not squash-merge** this PR. Use the "Create a merge commit" option to do a regular merge. ## Command line hint To perform the merge from the command line, you could do something like the following (where "origin" is the name of the remote in your local git repo): ``` git stash git fetch --all git checkout origin/feature/hybridChat git merge origin/master git commit git push origin HEAD:refs/heads/autoMerge/feature/hybridChat ``` --------- Signed-off-by: nkomonen-amazon Co-authored-by: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Co-authored-by: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Co-authored-by: nkomonen-amazon --- ...-489e72aa-bb0d-4964-bd84-002f25db6b5f.json | 4 + packages/amazonq/package.json | 4 + .../amazonq/test/e2e/inline/inline.test.ts | 2 +- packages/core/src/auth/sso/clients.ts | 4 +- packages/core/src/codewhisperer/activation.ts | 5 + .../core/src/codewhisperer/commands/types.ts | 3 + .../core/src/codewhisperer/region/utils.ts | 49 ++++++++++ .../core/src/codewhisperer/util/authUtil.ts | 1 + .../core/src/shared/settings-amazonq.gen.ts | 3 +- .../src/shared/utilities/collectionUtils.ts | 21 ++++- .../src/shared/utilities/textUtilities.ts | 2 +- packages/core/src/shared/vscode/commands2.ts | 2 +- .../shared/utilities/collectionUtils.test.ts | 91 ++++++++++++------- 13 files changed, 148 insertions(+), 43 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json create mode 100644 packages/core/src/codewhisperer/region/utils.ts diff --git a/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json b/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json new file mode 100644 index 00000000000..0cd71188f81 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Toast message to warn users if Developer Profile is not selected" +} diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 1d285913708..2393b574f1e 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -135,6 +135,10 @@ "amazonQChatPairProgramming": { "type": "boolean", "default": false + }, + "amazonQSelectDeveloperProfile": { + "type": "boolean", + "default": false } }, "additionalProperties": false 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/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 efebb01e179..b0aa54e17a0 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -95,6 +95,7 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr import { setContext } from '../shared/vscode/setContext' import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' import { detectCommentAboveLine } from '../shared/utilities/commentUtils' +import { notifySelectDeveloperProfile } from './region/utils' let localize: nls.LocalizeFunc @@ -380,6 +381,10 @@ export async function activate(context: ExtContext): Promise { await auth.notifySessionConfiguration() } } + + if (auth.requireProfileSelection()) { + await notifySelectDeveloperProfile() + } }, { emit: false, functionId: { name: 'activateCwCore' } } ) 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/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/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index 0898493b6db..f4cc90a5293 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -46,6 +46,7 @@ import { withTelemetryContext } from '../../shared/telemetry/util' import { focusAmazonQPanel } from '../../codewhispererChat/commands/registerCommands' import { throttle } from 'lodash' import { RegionProfileManager } from '../region/regionProfileManager' + /** Backwards compatibility for connections w pre-chat scopes */ export const codeWhispererCoreScopes = [...scopesCodeWhispererCore] export const codeWhispererChatScopes = [...codeWhispererCoreScopes, ...scopesCodeWhispererChat] diff --git a/packages/core/src/shared/settings-amazonq.gen.ts b/packages/core/src/shared/settings-amazonq.gen.ts index 88324e10475..637c5b1b12e 100644 --- a/packages/core/src/shared/settings-amazonq.gen.ts +++ b/packages/core/src/shared/settings-amazonq.gen.ts @@ -22,7 +22,8 @@ export const amazonqSettings = { "amazonQLspManifestMessage": {}, "amazonQWorkspaceLspManifestMessage": {}, "amazonQChatDisclaimer": {}, - "amazonQChatPairProgramming": {} + "amazonQChatPairProgramming": {}, + "amazonQSelectDeveloperProfile": {} }, "amazonQ.showCodeWithReferences": {}, "amazonQ.allowFeatureDevelopmentToRunCodeAndTests": {}, diff --git a/packages/core/src/shared/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index 9f9fe9875b9..8a428b8e8b7 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) } } diff --git a/packages/core/src/shared/utilities/textUtilities.ts b/packages/core/src/shared/utilities/textUtilities.ts index 53c2e2be32c..ed1e1619122 100644 --- a/packages/core/src/shared/utilities/textUtilities.ts +++ b/packages/core/src/shared/utilities/textUtilities.ts @@ -10,7 +10,7 @@ import { default as stripAnsi } from 'strip-ansi' import { getLogger } from '../logger/logger' /** - * 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. * 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/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...' }], }) }) }) From 3549e338d8209cdcf4d2e24fe9c6796dcf828e64 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:00:27 -0700 Subject: [PATCH 068/153] fix(amazonq): fix enterprise users not able to sign in correctly if they have 2+ vscode instances open (#7151) ## Problem revision of #7134, this pr aims to address the comment from the previous PR https://github.com/aws/aws-toolkit-vscode/pull/7134#discussion_r2056518840 to extract the logic to a shared module so the diff against 7134 is expected to be large. after deployment 4/21, service has a strict 1 tps throttling policy and there was no caching for the API call previously. It will impact users as long as they have multiple ide instances opened as all of them will make list profile call while users attempt to sign in. ## Solution 1. cache the api result for 60 seconds for reuse 2. use lock to prevent multiple instances trying to call at the same time. Only 1 will make the service call and the rest will wait until it's done and read from the cache. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- ...-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json | 4 + .../region/regionProfileManager.ts | 35 +++- .../core/src/codewhisperer/util/authUtil.ts | 2 + .../webview/vue/amazonq/backend_amazonq.ts | 2 +- packages/core/src/shared/globalState.ts | 1 + packages/core/src/shared/logger/logger.ts | 1 + .../src/shared/utilities/resourceCache.ts | 190 ++++++++++++++++++ 7 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json create mode 100644 packages/core/src/shared/utilities/resourceCache.ts diff --git a/packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json b/packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json new file mode 100644 index 00000000000..087dd85d88d --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Fix users can not log in successfully with 2+ IDE instnaces open due to throttle error throw by the service" +} diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index 039d5cefb59..a28ac46ee1c 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -29,6 +29,7 @@ import { isAwsError, ToolkitError } from '../../shared/errors' import { telemetry } from '../../shared/telemetry/telemetry' import { localize } from '../../shared/utilities/vsCodeUtils' 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 = { @@ -59,6 +60,27 @@ export class RegionProfileManager { // 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[] = [] + 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() { const conn = this.connectionProvider() if (isBuilderIdConnection(conn)) { @@ -104,6 +126,10 @@ export class RegionProfileManager { constructor(private readonly connectionProvider: () => Connection | undefined) {} + async getProfiles(): Promise { + return this.cache.getResource() + } + async listRegionProfile(): Promise { this._profiles = [] @@ -238,7 +264,7 @@ export class RegionProfileManager { return } // cross-validation - this.listRegionProfile() + this.getProfiles() .then(async (profiles) => { const r = profiles.find((it) => it.arn === previousSelected.arn) if (!r) { @@ -300,7 +326,7 @@ export class RegionProfileManager { const selected = this.activeRegionProfile let profiles: RegionProfile[] = [] try { - profiles = await this.listRegionProfile() + profiles = await this.getProfiles() } catch (e) { return [ { @@ -347,6 +373,11 @@ export class RegionProfileManager { } } + // Should be called on connection changed in case users change to a differnet connection and use the wrong resultset. + async clearCache() { + await this.cache.clearCache() + } + async createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { const token = (await conn.getToken()).accessToken const serviceOption: ServiceOptions = { diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index f4cc90a5293..2f8843d754b 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -144,6 +144,8 @@ export class AuthUtil { if (!this.isConnected()) { await this.regionProfileManager.invalidateProfile(this.regionProfileManager.activeRegionProfile?.arn) } + + await this.regionProfileManager.clearCache() }) this.regionProfileManager.onDidChangeRegionProfile(async () => { 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 0853f80e952..6ed152ab4ea 100644 --- a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts +++ b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts @@ -223,7 +223,7 @@ export class AmazonQLoginWebview extends CommonAuthWebview { */ override async listRegionProfiles(): Promise { try { - return await AuthUtil.instance.regionProfileManager.listRegionProfile() + return await AuthUtil.instance.regionProfileManager.getProfiles() } catch (e) { const conn = AuthUtil.instance.conn as SsoConnection | undefined telemetry.amazonq_didSelectProfile.emit({ diff --git a/packages/core/src/shared/globalState.ts b/packages/core/src/shared/globalState.ts index 44d848ec69d..6ecca56f90a 100644 --- a/packages/core/src/shared/globalState.ts +++ b/packages/core/src/shared/globalState.ts @@ -49,6 +49,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' diff --git a/packages/core/src/shared/logger/logger.ts b/packages/core/src/shared/logger/logger.ts index 9b4bead6a37..eac564b9c35 100644 --- a/packages/core/src/shared/logger/logger.ts +++ b/packages/core/src/shared/logger/logger.ts @@ -18,6 +18,7 @@ export type LogTopic = | 'chat' | 'stepfunctions' | 'unknown' + | 'resourceCache' class ErrorLog { constructor( diff --git a/packages/core/src/shared/utilities/resourceCache.ts b/packages/core/src/shared/utilities/resourceCache.ts new file mode 100644 index 00000000000..8189f40a4c5 --- /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 + } else { + 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, + } + logger.info(`doen loading latest resource, updating resource cache: %s`, this.key) + await this.releaseLock(r) + 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(`try obtain resource cache read write lock for resource %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 + } +} From 4ceed6e47600fb90b5f133d97b167104aea5112a Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:06:21 -0400 Subject: [PATCH 069/153] fix(amazonq): handle explain, refactor, fix, optimize, sendToPrompt (#7169) ## Problem explain, refactor, fix, optimize right click menus aren't working ## Solution re-enable them through flare's genericCommand message ## TODO - handle generateUnitTests - handle explainIssue both of these need flare API changes to send generic prompts from the frontend to the backend --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/commands.ts | 82 +++++++++---------- packages/core/src/codewhispererChat/app.ts | 2 +- .../commands/registerCommands.ts | 75 ++--------------- 3 files changed, 49 insertions(+), 110 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 8a84883b9a7..8cdd33cafe2 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -4,7 +4,7 @@ */ import { Commands, globals } from 'aws-core-vscode/shared' -// import { window } from 'vscode' +import { window } from 'vscode' import { AmazonQChatViewProvider } from './webviewProvider' /** @@ -13,21 +13,21 @@ import { AmazonQChatViewProvider } from './webviewProvider' */ 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.sendToPrompt', (data) => { - // const triggerType = getCommandTriggerType(data) - // const selection = getSelectedText() + 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.sendToPrompt', (data) => { + const triggerType = getCommandTriggerType(data) + const selection = getSelectedText() - // void focusAmazonQPanel().then(() => { - // void provider.webview?.postMessage({ - // command: 'sendToPrompt', - // params: { selection: selection, triggerType }, - // }) - // }) - // }), + void focusAmazonQPanel().then(() => { + void provider.webview?.postMessage({ + command: 'sendToPrompt', + params: { selection: selection, triggerType }, + }) + }) + }), Commands.register('aws.amazonq.openTab', () => { void focusAmazonQPanel().then(() => { void provider.webview?.postMessage({ @@ -39,36 +39,36 @@ export function registerCommands(provider: AmazonQChatViewProvider) { ) } -// function getSelectedText(): string { -// const editor = window.activeTextEditor -// if (editor) { -// const selection = editor.selection -// const selectedText = editor.document.getText(selection) -// return selectedText -// } +function getSelectedText(): string { + const editor = window.activeTextEditor + if (editor) { + const selection = editor.selection + const selectedText = editor.document.getText(selection) + return selectedText + } -// return ' ' -// } + return ' ' +} -// function getCommandTriggerType(data: any): string { -// // 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 ? 'hotkeys' : 'contextMenu' -// } +function getCommandTriggerType(data: any): string { + // 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 ? 'hotkeys' : 'contextMenu' +} -// function registerGenericCommand(commandName: string, genericCommand: string, provider: AmazonQChatViewProvider) { -// return Commands.register(commandName, (data) => { -// const triggerType = getCommandTriggerType(data) -// const selection = getSelectedText() +function registerGenericCommand(commandName: string, genericCommand: string, provider: AmazonQChatViewProvider) { + return Commands.register(commandName, (data) => { + const triggerType = getCommandTriggerType(data) + const selection = getSelectedText() -// void focusAmazonQPanel().then(() => { -// void provider.webview?.postMessage({ -// command: 'genericCommand', -// params: { genericCommand, selection, triggerType }, -// }) -// }) -// }) -// } + void focusAmazonQPanel().then(() => { + void provider.webview?.postMessage({ + command: 'genericCommand', + params: { genericCommand, selection, triggerType }, + }) + }) + }) +} /** * Importing focusAmazonQPanel from aws-core-vscode/amazonq leads to several dependencies down the chain not resolving since AmazonQ chat 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..13a2db2de95 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,13 @@ 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.explainIssue', async (issue) => {}) + Commands.register('aws.amazonq.generateUnitTests', async (data) => {}) + Commands.register('aws.amazonq.updateContextCommandItems', () => {}) } export type EditorContextBaseCommandType = From f2eabbd1c3e05404ef6ad5907d0c75686fbb6011 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:33:30 -0700 Subject: [PATCH 070/153] fix(resourceCache): cache not used by 2nd vscode instance ## Problem commit 97a44821ee9bb65cdc737011e81dc6e87ab23136 breaks the cache by resetting the flag/lock on connection changed. When users sign in and have multiple VSCode instances, all windows will get a `onConnectionChanged` hence overwrite the lock/flag and load the resource from live. ## Solution Only clear the cache when users sign out, and it's sufficient for "change connection" use case as well. (users have to sign out before connecting with different connection) --- packages/core/src/codewhisperer/util/authUtil.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index 2f8843d754b..10acbe16424 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -143,9 +143,8 @@ export class AuthUtil { if (!this.isConnected()) { await this.regionProfileManager.invalidateProfile(this.regionProfileManager.activeRegionProfile?.arn) + await this.regionProfileManager.clearCache() } - - await this.regionProfileManager.clearCache() }) this.regionProfileManager.onDidChangeRegionProfile(async () => { From 3171de5e4a90c9e8b4aec0965fcd5c9bc0235745 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 25 Apr 2025 15:33:59 -0700 Subject: [PATCH 071/153] fix(resourcecache): improve logging #7175 --- .../src/shared/utilities/resourceCache.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/core/src/shared/utilities/resourceCache.ts b/packages/core/src/shared/utilities/resourceCache.ts index 8189f40a4c5..c0beee61cd6 100644 --- a/packages/core/src/shared/utilities/resourceCache.ts +++ b/packages/core/src/shared/utilities/resourceCache.ts @@ -68,7 +68,7 @@ export abstract class CachedResource { const duration = now() - resource.timestamp if (duration < this.expirationInMilli) { logger.debug( - `cache hit, duration(%sms) is less than expiration(%sms), returning cached value %s`, + `cache hit, duration(%sms) is less than expiration(%sms), returning cached value: %s`, duration, this.expirationInMilli, this.key @@ -76,16 +76,16 @@ export abstract class CachedResource { // release the lock await this.releaseLock(resource, cachedValue) return resource.result - } else { - logger.debug( - `cache is stale, duration(%sms) is older than expiration(%sms), pulling latest resource %s`, - duration, - this.expirationInMilli, - this.key - ) } + + 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) + logger.info(`cache miss, pulling latest resource: %s`, this.key) } /** @@ -104,8 +104,8 @@ export abstract class CachedResource { timestamp: now(), result: latest, } - logger.info(`doen loading latest resource, updating resource cache: %s`, this.key) 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) @@ -129,7 +129,7 @@ export abstract class CachedResource { const lock = await waitUntil(async () => { const lock = await _acquireLock() - logger.debug(`try obtain resource cache read write lock for resource %s`, this.key) + logger.debug(`trying to acquire resource cache lock: %s`, this.key) if (lock) { return lock } From 3072e02526dbbd5f875fdcd7c30a6de12d9a9dbc Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 25 Apr 2025 22:48:33 +0000 Subject: [PATCH 072/153] Release 1.62.0 --- package-lock.json | 4 ++-- packages/amazonq/.changes/1.62.0.json | 14 ++++++++++++++ ...g Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json | 4 ---- ...g Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json | 4 ---- packages/amazonq/CHANGELOG.md | 5 +++++ packages/amazonq/package.json | 2 +- 6 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 packages/amazonq/.changes/1.62.0.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json diff --git a/package-lock.json b/package-lock.json index 9afa7c62511..26a7a793dd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26694,7 +26694,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.62.0-SNAPSHOT", + "version": "1.62.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json b/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json deleted file mode 100644 index 0cd71188f81..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-489e72aa-bb0d-4964-bd84-002f25db6b5f.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Toast message to warn users if Developer Profile is not selected" -} diff --git a/packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json b/packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json deleted file mode 100644 index 087dd85d88d..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-8290a06f-ee3f-4235-b8af-faedb1bdfbe4.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Fix users can not log in successfully with 2+ IDE instnaces open due to throttle error throw by the service" -} diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index 3f1fedb6305..9bd9b737b06 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,8 @@ +## 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 diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index cb74db00761..6988b62d0b3 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.62.0-SNAPSHOT", + "version": "1.62.0", "extensionKind": [ "workspace" ], From b234197c65ebfc44ed69cc2919c57159aeb70792 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 25 Apr 2025 22:49:06 +0000 Subject: [PATCH 073/153] Release 3.56.0 --- package-lock.json | 4 ++-- packages/toolkit/.changes/3.56.0.json | 5 +++++ packages/toolkit/CHANGELOG.md | 4 ++++ packages/toolkit/package.json | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 packages/toolkit/.changes/3.56.0.json diff --git a/package-lock.json b/package-lock.json index 9afa7c62511..547bcc13397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28559,7 +28559,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.56.0-SNAPSHOT", + "version": "3.56.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/CHANGELOG.md b/packages/toolkit/CHANGELOG.md index ede66857244..aa29ed25324 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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 a260146c847..1ad4f21af5d 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.56.0", "extensionKind": [ "workspace" ], From 3beb8b226a15a1442a59ac9193de332a556c795d Mon Sep 17 00:00:00 2001 From: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:03:13 -0700 Subject: [PATCH 074/153] fix(amazonq): enable local context for falcon (#7176) ## Problem context items should be always on and not controlled by workspace setting there need to be a separate setting to control @workspace indexing, out of scope for this PR ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 1373c026a9c..b198aabfd6f 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -92,7 +92,8 @@ export async function startLanguageServer( customization, optOutTelemetry: getOptOutPreference() === 'OPTOUT', projectContext: { - enableLocalIndexing: CodeWhispererSettings.instance.isLocalIndexEnabled(), + // TODO: we might need another setting to control the actual indexing + enableLocalIndexing: true, enableGpuAcceleration: CodeWhispererSettings.instance.isLocalIndexGPUEnabled(), indexWorkerThreads: CodeWhispererSettings.instance.getIndexWorkerThreads(), localIndexing: { From 947c471967e06ffb8a39c6f06630d38ffd20dbee Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Fri, 25 Apr 2025 20:14:33 -0700 Subject: [PATCH 075/153] feat(amazonq): support multiple uri in a single diff scheme (#7167) ## Problem The current viewDiff implementation is restricted to only allowing comparing the current file contents. This means it's not possible to view a diff for a change that was applied at a previous point in time. ## Solution - Create a new `DiffContentProvider` that can support showing multiple URIs in the same scheme. The current `ContentProvider` has a limitation that it can only show a single URI. - Refactor the code to use a defined type `ViewDiffMessage` instead of `any` - Change the logic so that diffs are stored in memory instead of a temp file on disk --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- package-lock.json | 8 +- packages/amazonq/src/lsp/chat/messages.ts | 32 ++-- packages/core/package.json | 2 +- .../commons/controllers/contentController.ts | 32 ++-- .../controllers/diffContentProvider.ts | 51 +++++++ packages/core/src/amazonq/index.ts | 2 +- .../shared/utilities/textDocumentUtilities.ts | 75 ++++++++- .../src/shared/utilities/textUtilities.ts | 6 +- .../controllers/contentController.test.ts | 143 ++++++++++++++++++ .../utilities/textDocumentUtilities.test.ts | 140 +++++++++++++++++ 10 files changed, 461 insertions(+), 30 deletions(-) create mode 100644 packages/core/src/amazonq/commons/controllers/diffContentProvider.ts create mode 100644 packages/core/src/test/amazonq/commons/controllers/contentController.test.ts create mode 100644 packages/core/src/test/shared/utilities/textDocumentUtilities.test.ts diff --git a/package-lock.json b/package-lock.json index 8ddd858085d..36222058e8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10829,9 +10829,9 @@ } }, "node_modules/@aws/language-server-runtimes-types": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@aws/language-server-runtimes-types/-/language-server-runtimes-types-0.1.22.tgz", - "integrity": "sha512-cyNrq6TqCcD9+vYUvvXJ5EJzfB4DrLtDBzBXgv/4zPIMRH0YwGEsRZLzPDwCPCxuZ5kGlal3GlBMkLkMCRGPdQ==", + "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": { @@ -26484,7 +26484,7 @@ "@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.21", + "@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", diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 0427a8610a3..6d2d9947dde 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -59,7 +59,12 @@ import * as jose from 'jose' import { AmazonQChatViewProvider } from './webviewProvider' import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' -import { DefaultAmazonQAppInitContext, messageDispatcher, EditorContentController } from 'aws-core-vscode/amazonq' +import { + DefaultAmazonQAppInitContext, + messageDispatcher, + EditorContentController, + ViewDiffMessage, +} from 'aws-core-vscode/amazonq' import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry' import { isValidResponseError } from './error' @@ -449,17 +454,24 @@ export function registerMessageListeners( new vscode.Position(0, 0), new vscode.Position(doc.lineCount - 1, doc.lineAt(doc.lineCount - 1).text.length) ) - await ecc.viewDiff( - { - context: { - activeFileContext: { filePath: params.originalFileUri }, - focusAreaContext: { selectionInsideExtendedCodeBlock: entireDocumentSelection }, + 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 ?? '', }, - amazonQDiffScheme, - true - ) + code: params.fileContent ?? '', + } + await ecc.viewDiff(viewDiffMessage, amazonQDiffScheme) }) languageClient.onNotification(chatUpdateNotificationType.method, (params: ChatUpdateParams) => { diff --git a/packages/core/package.json b/packages/core/package.json index 31d928da9fa..0ed5e368121 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -444,7 +444,7 @@ "@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.21", + "@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", diff --git a/packages/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index 2586951a18e..70744417451 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,11 @@ 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 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) {} @@ -155,26 +160,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, reverseOrder = false) { + 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 !== undefined && selection) { - const originalFileUri = vscode.Uri.file(filePath) - const uri = await createTempFileForDiff(originalFileUri, message, selection, scheme) - // 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', - ...(reverseOrder ? [uri, originalFileUri] : [originalFileUri, uri]), + originalFileUri, + 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/index.ts b/packages/core/src/amazonq/index.ts index 8a4815c1577..168915a8434 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -47,7 +47,7 @@ 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 } from './commons/controllers/contentController' +export { EditorContentController, ViewDiffMessage } from './commons/controllers/contentController' /** * main from createMynahUI is a purely browser dependency. Due to this 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 ed1e1619122..6bc1a4eff21 100644 --- a/packages/core/src/shared/utilities/textUtilities.ts +++ b/packages/core/src/shared/utilities/textUtilities.ts @@ -8,6 +8,7 @@ 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 has or exceeds `n` chars. @@ -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/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/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) + }) + }) +}) From 969e07898dd35be9635f9795020d3a87fab0c772 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Mon, 28 Apr 2025 14:18:54 +0000 Subject: [PATCH 076/153] Update version to snapshot version: 1.63.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/amazonq/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26a7a793dd0..b079db4d347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26694,7 +26694,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.62.0", + "version": "1.63.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 6988b62d0b3..c8f64556b31 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.62.0", + "version": "1.63.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 2a29d122c07ea957b0fd01c3bf02145d92572199 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Mon, 28 Apr 2025 14:20:19 +0000 Subject: [PATCH 077/153] Update version to snapshot version: 3.57.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/toolkit/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 547bcc13397..e2ab5efc339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28559,7 +28559,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.56.0", + "version": "3.57.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 1ad4f21af5d..d893c7c95e5 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", + "version": "3.57.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 2f103ffaf42c318b6b963d8744400737e915c54c Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Mon, 28 Apr 2025 13:01:00 -0400 Subject: [PATCH 078/153] fix(amazonq): Remove the 'Cancel' button on LSP download (#7184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just remove the 'Cancel' button for now when downloading the language server. We do not want to deal with the edge case of a user cancelling part way and then their Q features don't work since it depends on the language server. Screenshot 2025-04-28 at 12 18 54 PM --- TODO: Figure out a better solution for allowing users to cancel the LS download, but then making it easy to download again if they eventually want it. We will also need to design it so they are aware that cancelling will prevent Q features from working. This will require an update to the spec. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/core/src/shared/lsp/lspResolver.ts | 13 +++++++++++-- packages/core/src/shared/utilities/messages.ts | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core/src/shared/lsp/lspResolver.ts b/packages/core/src/shared/lsp/lspResolver.ts index 70f66cafd14..ba51030e348 100644 --- a/packages/core/src/shared/lsp/lspResolver.ts +++ b/packages/core/src/shared/lsp/lspResolver.ts @@ -13,9 +13,10 @@ 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. 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. @@ -106,7 +107,15 @@ export class LanguageServerResolver { */ private async showDownloadProgress() { const timeout = new Timeout(remoteDownloadTimeout) - await showMessageWithCancel(`Downloading '${this.lsName}' language server`, timeout) + void showProgressWithTimeout( + { + title: `Downloading '${this.lsName}' language server`, + location: vscode.ProgressLocation.Notification, + cancellable: false, + }, + timeout, + 0 + ) return timeout } 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 From c6afcb62f995f54bb41af304483221294c09d172 Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Mon, 28 Apr 2025 15:21:37 -0400 Subject: [PATCH 079/153] refactor(amazonq): Change Q LSP downloading message text (#7185) ## PROBLEM: When installing the LSP for Q we get a message that says `"Installing 'Amazon Q' language server"`, but they want it to be slightly different (`"Updating Amazon Q plugin"`). The issue is the base class for the lsp installer class has a generic message that is not easy to customize. ## SOLUTION: Allow an override downloading message to be defined in each specific LspInstaller implementation. This message is then routed in to the LspResolver (the thing that downloads the Lsp), and the message is displayed here. This override message will show when the download is happening. Everyone is happy. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/amazonq/src/lsp/lspInstaller.ts | 2 ++ packages/core/src/shared/lsp/baseLspInstaller.ts | 9 ++++++++- packages/core/src/shared/lsp/lspResolver.ts | 12 ++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) 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/core/src/shared/lsp/baseLspInstaller.ts b/packages/core/src/shared/lsp/baseLspInstaller.ts index d130b75f339..7388ca32f80 100644 --- a/packages/core/src/shared/lsp/baseLspInstaller.ts +++ b/packages/core/src/shared/lsp/baseLspInstaller.ts @@ -44,7 +44,8 @@ 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 ba51030e348..0baa9f89730 100644 --- a/packages/core/src/shared/lsp/lspResolver.ts +++ b/packages/core/src/shared/lsp/lspResolver.ts @@ -23,12 +23,20 @@ import vscode from 'vscode' 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, + /** + * 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: @@ -109,7 +117,7 @@ export class LanguageServerResolver { const timeout = new Timeout(remoteDownloadTimeout) void showProgressWithTimeout( { - title: `Downloading '${this.lsName}' language server`, + title: this.downloadMessage, location: vscode.ProgressLocation.Notification, cancellable: false, }, From db673c9b74b36591bb5642b3da7d4bc7ae2afaf4 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Mon, 28 Apr 2025 18:11:17 -0400 Subject: [PATCH 080/153] feat(amazonq): sync lsp logging with local configuration. (#7186) ## Problem Same problem as https://github.com/aws/aws-toolkit-vscode/pull/7172 ## Solution - listen to logLevel change events, and forward them to LSP. - pass initial logLevel to the LSP as well. - This requires mapping the local levels to the Flare Levels. This is done with the following: ``` export 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. ]) ``` ## Notes - Because of this mapping, it means that to enabled 'debug' logs in Flare, we set to 'trace' in VSC which can be confusing for contributors. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/activation.ts | 39 +++++++--- packages/amazonq/src/lsp/client.ts | 81 +++++++++++--------- packages/amazonq/src/lsp/config.ts | 30 +++++++- packages/core/src/shared/extensionGlobals.ts | 4 +- packages/core/src/shared/logger/logger.ts | 5 -- 5 files changed, 105 insertions(+), 54 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index 33795219bff..9dd1d31c3de 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -85,6 +85,12 @@ export async function activate(languageClient: LanguageClient, encryptionKey: Bu 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', + }) }) ) } @@ -98,16 +104,24 @@ export async function activate(languageClient: LanguageClient, encryptionKey: Bu * push the given config. */ async function pushConfigUpdate(client: LanguageClient, config: QConfigs) { - if (config.type === 'profile') { - await client.sendRequest(updateConfigurationRequestType.method, { - section: 'aws.q', - settings: { profileArn: config.profileArn }, - }) - } else if (config.type === 'customization') { - client.sendNotification(DidChangeConfigurationNotification.type.method, { - section: 'aws.q', - settings: { customization: config.customization }, - }) + 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 = { @@ -118,4 +132,7 @@ type CustomizationConfig = { type: 'customization' customization: string | undefined } -type QConfigs = ProfileConfig | CustomizationConfig +type LogLevelConfig = { + type: 'logLevel' +} +type QConfigs = ProfileConfig | CustomizationConfig | LogLevelConfig diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index b198aabfd6f..aa1c1a8184f 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -36,6 +36,7 @@ import { } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' +import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config' const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') @@ -80,42 +81,11 @@ export async function startLanguageServer( */ configuration: async (params, token, next) => { const config = await next(params, token) - if (params.items[0].section === 'aws.q') { - const customization = undefinedIfEmpty(getSelectedCustomization().arn) - /** - * 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, - optOutTelemetry: getOptOutPreference() === 'OPTOUT', - projectContext: { - // TODO: we might need another setting to control the actual indexing - enableLocalIndexing: true, - 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(), - }, - }, - }, - ] + const section = params.items[0].section + if (!isValidConfigSection(section)) { + return config } - if (params.items[0].section === 'aws.codeWhisperer') { - return [ - { - includeSuggestionsWithCodeReferences: - CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled(), - shareCodeWhispererContentWithAWS: !CodeWhispererSettings.instance.isOptoutEnabled(), - }, - ] - } - return config + return getConfigSection(section) }, }, }, @@ -139,6 +109,7 @@ export async function startLanguageServer( showSaveFileDialog: true, }, }, + logLevel: toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel), }, credentials: { providesBearerToken: true, @@ -296,3 +267,43 @@ function onServerRestartHandler(client: LanguageClient, auth: AmazonQLspAuth) { await auth.refreshConnection(true) }) } + +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: { + // TODO: we might need another setting to control the actual indexing + enableLocalIndexing: true, + 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(), + }, + ] + 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 62bba1ac93d..c9c9b0df5ac 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,6 +10,26 @@ 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://d3akiidp1wvqyg.cloudfront.net/qAgenticChatServer/0/manifest.json', // TODO swap this back supportedVersions: '*', // TODO swap this back @@ -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/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/logger/logger.ts b/packages/core/src/shared/logger/logger.ts index eac564b9c35..85df7b4e1f8 100644 --- a/packages/core/src/shared/logger/logger.ts +++ b/packages/core/src/shared/logger/logger.ts @@ -105,11 +105,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: From 6dbb21e50e539c5973586714295e3ce066b030ef Mon Sep 17 00:00:00 2001 From: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:24:44 -0700 Subject: [PATCH 081/153] fix(amazonq): fix extension name in lsp initialization (#7189) ## Problem the extension name doesn't match what flare expects: https://github.com/aws/language-servers/blob/main/server/aws-lsp-codewhisperer/src/shared/telemetryUtils.ts#L50 ## Solution update it --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index aa1c1a8184f..23633f15048 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -23,7 +23,6 @@ import { import { AuthUtil, CodeWhispererSettings, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' import { Settings, - oidcClientName, createServerOptions, globals, Experiments, @@ -95,7 +94,7 @@ export async function startLanguageServer( name: env.appName, version: version, extension: { - name: oidcClientName(), + name: 'AmazonQ-For-VSCode', version: '0.0.1', }, clientId: crypto.randomUUID(), From 8f3fb4413ad698c239cc31c987ee97132b13ed0d Mon Sep 17 00:00:00 2001 From: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:55:22 -0700 Subject: [PATCH 082/153] fix(amazonq): type error for workspaceIndexCacheDirPath (#7191) ## Problem set default to empty string instead of undefined for workspaceIndexCacheDirPath ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/core/src/codewhisperer/util/codewhispererSettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/util/codewhispererSettings.ts b/packages/core/src/codewhisperer/util/codewhispererSettings.ts index 4c30c0df467..80f5d1f2a0d 100644 --- a/packages/core/src/codewhisperer/util/codewhispererSettings.ts +++ b/packages/core/src/codewhisperer/util/codewhispererSettings.ts @@ -67,7 +67,7 @@ export class CodeWhispererSettings extends fromExtensionManifest('amazonQ', desc } public getIndexCacheDirPath(): string { - return this.get('workspaceIndexCacheDirPath', undefined) + return this.get('workspaceIndexCacheDirPath', '') } public getIndexIgnoreFilePatterns(): string[] { From b60036b6c2ed8211692d8d267b3f45a5a9c41f99 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:15:42 -0400 Subject: [PATCH 083/153] fix(amazonq): retry LSP token refresh on recoverable errors only. (#7192) ## Problem Some users were having invalid bearer token exceptions when making chat requests through agentic chat. This highlights an issue in how we sync the token. Originally, we thought this was because of a race condition. We check the token every 10 seconds for changes, and if its expired, refresh it and send it to the language server. However, that still leaves a 10 second gap where the language server could use an expired token before we update it. However, we add a buffer of one minute to our `isExpired` check here: https://github.com/aws/aws-toolkit-vscode/blob/db673c9b74b36591bb5642b3da7d4bc7ae2afaf4/packages/core/src/auth/sso/model.ts#L160 Therefore, this causes the token to expire a minute early, meaning there is a full minute, where our checks should detect the expired token and refresh it both locally and on the language server. However, they don't because the checks aren't actually being made. This is because of how we handle token refresh errors. Note the following behavior: - if refresh throws a recoverable error, we throw it. See [here](https://github.com/aws/aws-toolkit-vscode/blob/6dbb21e50e539c5973586714295e3ce066b030ef/packages/core/src/auth/auth.ts#L856-L878). - if refresh throws a non-recoverable error we invalidate the connection. see [here](https://github.com/aws/aws-toolkit-vscode/blob/6dbb21e50e539c5973586714295e3ce066b030ef/packages/core/src/auth/auth.ts#L893-L948). The current `refreshConnection` logic doesn't work with this because we continuously try to refresh until it throws an error. However, based on the behavior above, we do want to retry on refresh errors since that means the error is recoverable! Additionally, we don't want to retry when token refreshes result in an invalid connection because those are nonrecoverable errors. ## Solution - Refactor our token refreshes to log errors thrown and continue to retry (since these are implicitly recoverable errors). - Avoid refreshing when the connection state is invalid (since this implies an unrecoverable error). ## Notes - Flare auth can't come soon enough. This behavior is not obvious to a consumer, and leads to bugs like this. - debugged w/ @nkomonen-amazon --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/auth.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/amazonq/src/lsp/auth.ts b/packages/amazonq/src/lsp/auth.ts index 6a0953c98ab..816c4b09ab0 100644 --- a/packages/amazonq/src/lsp/auth.ts +++ b/packages/amazonq/src/lsp/auth.ts @@ -66,20 +66,29 @@ export const notificationTypes = { * Facade over our VSCode Auth that does crud operations on the language server auth */ export class AmazonQLspAuth { - constructor(private readonly client: LanguageClient) {} + #logErrorIfChanged = onceChanged((s) => getLogger('amazonqLsp').error(s)) + constructor( + private readonly client: LanguageClient, + private readonly authUtil: AuthUtil = AuthUtil.instance + ) {} /** * @param force bypass memoization, and forcefully update the bearer token */ async refreshConnection(force: boolean = false) { - const activeConnection = AuthUtil.instance.auth.activeConnection - if (activeConnection?.type === 'sso') { + const activeConnection = this.authUtil.auth.activeConnection + if (activeConnection?.state === 'valid' && activeConnection?.type === 'sso') { // send the token to the language server - const token = await AuthUtil.instance.getBearerToken() + const token = await this.authUtil.getBearerToken() await (force ? this._updateBearerToken(token) : this.updateBearerToken(token)) } } + async logRefreshError(e: unknown) { + const err = e as Error + this.#logErrorIfChanged(`Unable to update bearer token: ${err.name}:${err.message}`) + } + public updateBearerToken = onceChanged(this._updateBearerToken.bind(this)) private async _updateBearerToken(token: string) { const request = await this.createUpdateCredentialsRequest({ @@ -93,10 +102,7 @@ export class AmazonQLspAuth { public startTokenRefreshInterval(pollingTime: number = oneMinute / 2) { const interval = setInterval(async () => { - await this.refreshConnection().catch((e) => { - getLogger('amazonqLsp').error('Unable to update bearer token: %s', (e as Error).message) - clearInterval(interval) - }) + await this.refreshConnection().catch((e) => this.logRefreshError(e)) }, pollingTime) return interval } From f1ede423b97fdca1edc032ff438b5987becb09d4 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Tue, 29 Apr 2025 08:01:55 -0400 Subject: [PATCH 084/153] fix(amazonq): open review tab for generate tests (#7195) ## Problem using the generate tests right click command won't work because a new tab never gets created, causing a "no more tabs available" warning ## Solution If we're using the right click -> generate tests command AND there isn't a /test tab open then create a new tab --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../src/amazonq/webview/ui/quickActions/handler.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts index d9572e13728..b492f939e11 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts @@ -155,7 +155,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 } @@ -169,6 +169,15 @@ export class QuickActionHandler { return } + /** + * 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) From 907c784f46bdf25f468e8eb0ede6f41297511d7e Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:32:45 -0400 Subject: [PATCH 085/153] fix(amazonq): explain issue fails (#7194) ## Problem - Explain issue from security scan hasn't been hooked up when moving to flare ## Solution - use sendToPrompt from flare to automatically submit an "explain message". This message contains a prompt that gets shown to the user and an escaped prompt that gets sent to the backend - text/formatting for uiMessage and contextMessage were taken from codewhisperer chat --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/commands.ts | 41 +++++++++++++++++++ .../commands/registerCommands.ts | 1 - 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 8cdd33cafe2..b24ec5f9bc8 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -6,6 +6,8 @@ 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' /** * TODO: Re-enable these once we can figure out which path they're going to live in @@ -17,6 +19,45 @@ export function registerCommands(provider: AmazonQChatViewProvider) { registerGenericCommand('aws.amazonq.refactorCode', 'Refactor', provider), registerGenericCommand('aws.amazonq.fixCode', 'Fix', provider), registerGenericCommand('aws.amazonq.optimizeCode', 'Optimize', provider), + 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/core/src/codewhispererChat/commands/registerCommands.ts b/packages/core/src/codewhispererChat/commands/registerCommands.ts index 13a2db2de95..75896047e90 100644 --- a/packages/core/src/codewhispererChat/commands/registerCommands.ts +++ b/packages/core/src/codewhispererChat/commands/registerCommands.ts @@ -40,7 +40,6 @@ export function registerCommands() { /** * make these no-ops, since theres still callers that need to be deprecated */ - Commands.register('aws.amazonq.explainIssue', async (issue) => {}) Commands.register('aws.amazonq.generateUnitTests', async (data) => {}) Commands.register('aws.amazonq.updateContextCommandItems', () => {}) } From 35e16865c34de460a90975e94623c8f8ca5bee38 Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:05:30 -0400 Subject: [PATCH 086/153] feat(amazonq): Log attribution notice when downloading language server (#7193) ## Problem: Legal requires us to log the attribution notice when downloading the language server. We do not currently do this. ## Solution: - What our message looks like: `lsp: Installing 'AmazonQ' Language Server v0.1.0-alpha.100 to /Users/nkomonen/Library/Caches/aws/toolkits/language-servers/AmazonQ/0.1.0-alpha.100 (Attribution notice can be found at https://dhi0h88q3j9q6.cloudfront.net/6a0c7aa8-8b63-43a7-b14b-ee7594a29c9d/THIRD_PARTY_LICENSES)` - This log message is in the generic part of our LSP downloader, and will show for all LSPs downloaded. The link to the attribution notice is already part of the manifest, so we grab it from there. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/core/src/shared/lsp/baseLspInstaller.ts | 2 +- packages/core/src/shared/lsp/lspResolver.ts | 12 +++++++++--- packages/core/src/shared/lsp/types.ts | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/shared/lsp/baseLspInstaller.ts b/packages/core/src/shared/lsp/baseLspInstaller.ts index 7388ca32f80..27e19df6b3a 100644 --- a/packages/core/src/shared/lsp/baseLspInstaller.ts +++ b/packages/core/src/shared/lsp/baseLspInstaller.ts @@ -41,7 +41,7 @@ export abstract class BaseLspInstaller { 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, @@ -236,8 +236,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) @@ -273,6 +273,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/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 { From 7fd3bf21eba9a06abb2382fcd904e91a015c752b Mon Sep 17 00:00:00 2001 From: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:09:46 -0700 Subject: [PATCH 087/153] fix(amazonq): stop workspace indexing in extension (#7197) ## Problem - we don't need workspace indexing on extension side anymore, the `isVectorIndexEnabled` setting controls if we build vector index used for @workspace feature, since now chat is moving to language server, the extension side workspace lsp should be only responsible for building BM25 index for inline, and not responsible for vector indexing for chat. ## Solution - stop vector indexing on extension - next step is to tie workspace index setting to language server workspace indexing, will be in next PR --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/app/chat/activation.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/amazonq/src/app/chat/activation.ts b/packages/amazonq/src/app/chat/activation.ts index 11275188440..bf6b7cdc3df 100644 --- a/packages/amazonq/src/app/chat/activation.ts +++ b/packages/amazonq/src/app/chat/activation.ts @@ -18,7 +18,7 @@ export async function activate(context: ExtensionContext) { void amazonq.LspController.instance.trySetupLsp(context, { startUrl: AuthUtil.instance.startUrl, maxIndexSize: CodeWhispererSettings.instance.getMaxIndexSize(), - isVectorIndexEnabled: CodeWhispererSettings.instance.isLocalIndexEnabled(), + isVectorIndexEnabled: false, }) }, 5000) @@ -30,14 +30,7 @@ export async function activate(context: ExtensionContext) { amazonq.listCodeWhispererCommandsWalkthrough.register(), amazonq.focusAmazonQPanel.register(), amazonq.focusAmazonQPanelKeybinding.register(), - amazonq.tryChatCodeLensCommand.register(), - vscode.workspace.onDidChangeConfiguration(async (configurationChangeEvent) => { - if (configurationChangeEvent.affectsConfiguration('amazonQ.workspaceIndex')) { - if (CodeWhispererSettings.instance.isLocalIndexEnabled()) { - void setupLsp() - } - } - }) + amazonq.tryChatCodeLensCommand.register() ) Commands.register('aws.amazonq.learnMore', () => { From bf63747e1a55949274a8366e0e4d2da6489f0b05 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:13:22 -0400 Subject: [PATCH 088/153] fix(amazonq): add event publisher for generateUnitTests (#7198) ## Problem `aws.amazonq.generateUnitTests` is not sending any events to /review ## Solution publish a chat message event to the /review tab to start I forgot to include these changes in https://github.com/aws/aws-toolkit-vscode/pull/7195 --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/commands.ts | 8 ++++++++ .../src/codewhispererChat/commands/registerCommands.ts | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index b24ec5f9bc8..74c63592a4f 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -8,6 +8,7 @@ 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 @@ -19,6 +20,13 @@ export function registerCommands(provider: AmazonQChatViewProvider) { 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() diff --git a/packages/core/src/codewhispererChat/commands/registerCommands.ts b/packages/core/src/codewhispererChat/commands/registerCommands.ts index 75896047e90..8ec1f7ac759 100644 --- a/packages/core/src/codewhispererChat/commands/registerCommands.ts +++ b/packages/core/src/codewhispererChat/commands/registerCommands.ts @@ -40,7 +40,6 @@ export function registerCommands() { /** * make these no-ops, since theres still callers that need to be deprecated */ - Commands.register('aws.amazonq.generateUnitTests', async (data) => {}) Commands.register('aws.amazonq.updateContextCommandItems', () => {}) } From 574990ef0f984f101ec3192d931e01059e6e1c5e Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:16:38 -0400 Subject: [PATCH 089/153] fix(amazonq): re-enable developer profile fetching in flare (#7190) Reverts previous PR #7118 --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/amazonq/src/lsp/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 23633f15048..0065b38af26 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -101,7 +101,7 @@ export async function startLanguageServer( }, awsClientCapabilities: { q: { - developerProfiles: false, + developerProfiles: true, }, window: { notifications: true, From f07432b449e30538a22a622c6070a93a5b6163bb Mon Sep 17 00:00:00 2001 From: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:23:32 -0700 Subject: [PATCH 090/153] fix(amazonq): tie enableLocalIndex back to user setting (#7200) ## Problem With https://github.com/aws/language-servers/pull/1201 we can now use userSetting to control vector indexing ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 0065b38af26..d813370cb0b 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -281,8 +281,7 @@ function getConfigSection(section: ConfigSection) { customization: undefinedIfEmpty(getSelectedCustomization().arn), optOutTelemetry: getOptOutPreference() === 'OPTOUT', projectContext: { - // TODO: we might need another setting to control the actual indexing - enableLocalIndexing: true, + enableLocalIndexing: CodeWhispererSettings.instance.isLocalIndexEnabled(), enableGpuAcceleration: CodeWhispererSettings.instance.isLocalIndexGPUEnabled(), indexWorkerThreads: CodeWhispererSettings.instance.getIndexWorkerThreads(), localIndexing: { From 00974f30064a1367c87f1758555d94503cc1d432 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 30 Apr 2025 08:19:06 -0400 Subject: [PATCH 091/153] feat(amazonq): add reference log for chat messages (#7206) ## Problem Chat results are never getting added to the reference tracker ## Solution If code references are available, add them to the reference tracker --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 14 +++++++++++--- packages/core/src/amazonq/commons/model.ts | 10 ++++++++++ packages/core/src/amazonq/index.ts | 2 +- packages/core/src/amazonqDoc/session/session.ts | 2 +- packages/core/src/amazonqFeatureDev/constants.ts | 8 -------- .../core/src/amazonqFeatureDev/session/session.ts | 4 +++- 6 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 6d2d9947dde..37acb43e7b9 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -57,13 +57,14 @@ 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 { 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' @@ -531,13 +532,17 @@ async function handlePartialResult( tabId: tabId, }) } + + for (const ref of decryptedMessage.codeReference ?? []) { + ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref)) + } } /** * 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, @@ -545,12 +550,15 @@ 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, }) + for (const ref of decryptedMessage.codeReference ?? []) { + ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref)) + } disposable.dispose() } 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 168915a8434..14c0e4a59a0 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -35,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' diff --git a/packages/core/src/amazonqDoc/session/session.ts b/packages/core/src/amazonqDoc/session/session.ts index b52474ffdd4..e3eb29d6d32 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 d2b174f0f6f..c1fc81a4701 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 = '' From 4f1f8b485fa03835f0f5e5dc8cc8f4bc6f006174 Mon Sep 17 00:00:00 2001 From: Frederic Mbea <117131783+mbfreder@users.noreply.github.com> Date: Wed, 30 Apr 2025 05:31:11 -0700 Subject: [PATCH 092/153] feat(lambda): user can invoke locally without debugging #7149 Add checkbox in local invoke webview so customers can choose to locally invoke with or without debugger --- .../lambda/vue/configEditor/samInvokeComponent.vue | 12 ++++++++++++ .../src/lambda/vue/configEditor/samInvokeFrontend.ts | 3 +++ ...Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json | 4 ++++ 3 files changed, 19 insertions(+) create mode 100644 packages/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json 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/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json b/packages/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json new file mode 100644 index 00000000000..1d7f7fe17ca --- /dev/null +++ b/packages/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "AppBuilder: unchecking the 'Attach a debugger' checkbox in local invoke webview invokes the function without a debugger" +} From d3db47aff9f1aebd531326ab57d2a23d7ad2b0cc Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 30 Apr 2025 09:26:57 -0400 Subject: [PATCH 093/153] fix(amazonq): enable agentic mode (#7199) ## Problem In [this PR](https://github.com/aws/language-servers/pull/1172/files#diff-3ef0f209f2cb39ae2f8b1c622f885a33cbf797f1882dfb4b22fb4cb17bcf901aR381) they're going to add a config setting for indicating we're in agentic mode ## Solution always enable it so we see the agentic UI --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/webviewProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index 6fac5a4afcf..1a513f1df3f 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -139,7 +139,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { const vscodeApi = acquireVsCodeApi() const hybridChatConnector = new HybridChatAdapter(${(await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected'},${featureConfigData},${welcomeCount},${disclaimerAcknowledged},${regionProfileString},${disabledCommands},${isSMUS},${isSM},vscodeApi.postMessage) const commands = [hybridChatConnector.initialQuickActions[0]] - qChat = amazonQChat.createChat(vscodeApi, {disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, quickActionCommands: commands}, hybridChatConnector, ${JSON.stringify(featureConfigData)}); + qChat = amazonQChat.createChat(vscodeApi, {disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, agenticMode: true, quickActionCommands: commands}, hybridChatConnector, ${JSON.stringify(featureConfigData)}); } window.addEventListener('message', (event) => { /** From e08a3bd2c27fec9bede298ec71146b5544f1370c Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:03:45 -0400 Subject: [PATCH 094/153] fix(amazonq): full project scan command not working (#7207) ## Problem a tab id isn't getting created when you call `status bar` -> `full project scan with /review` causing the command to fail with "you cannot open more then 10 tabs" ## Solution create a new tab if tabId is undefined. This can only happen when agent commands are triggered via a command, since they don't know the tabId ahead of time --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../src/amazonq/webview/ui/quickActions/handler.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts index b492f939e11..fe124d1fc0c 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts @@ -107,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 } @@ -126,6 +126,14 @@ export class QuickActionHandler { return } + /** + * 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) From c2f1210e73176d66aeac4d4ffec9c602688732a9 Mon Sep 17 00:00:00 2001 From: Tom Zu <138054255+tomcat323@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:46:14 -0400 Subject: [PATCH 095/153] =?UTF-8?q?fix(amazonq):=20dev=20profile=20selecti?= =?UTF-8?q?on=20breaks=20if=20certain=20regions=20aren't=20=E2=80=A6=20(#7?= =?UTF-8?q?203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Customer reported when a certain region is blocked(i.e. by cooperate network), it causes the Q developer profile list to break, and the customer can no longer use Q. ## Solution Don't throw immediately when a region is unavailable. So that the user still have access to working regions. Only throw if all regions are failing. ## Testing Shared the VSIX with customer and confirmed that issue is resolved. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- ...Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json | 4 ++++ .../src/codewhisperer/region/regionProfileManager.ts | 12 +++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json diff --git a/packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json b/packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json new file mode 100644 index 00000000000..61d1506ebe8 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Q profile selection hangs when a region is blocked" +} diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index a28ac46ee1c..aa51d163262 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -138,6 +138,8 @@ export class RegionProfileManager { return [] } const availableProfiles: RegionProfile[] = [] + const failedRegions: string[] = [] + for (const [region, endpoint] of endpoints.entries()) { const client = await this.createQClient(region, endpoint, conn as SsoConnection) const requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) => @@ -162,13 +164,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 listRegionProfile: ${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 From db976c932ba8cb34a40a928013b902ed728eb36e Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:03:53 -0400 Subject: [PATCH 096/153] fix(amazonq): duplicate code reference in reference log (#7208) ## Problem We're currently adding the reference log on partial responses and on complete responses, causing duplicate messages ## Solution on flare side we're combining all code references into the final response, so we just need to emit once --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 37acb43e7b9..9578858b708 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -532,10 +532,6 @@ async function handlePartialResult( tabId: tabId, }) } - - for (const ref of decryptedMessage.codeReference ?? []) { - ReferenceLogViewProvider.instance.addReferenceLog(referenceLogText(ref)) - } } /** @@ -556,6 +552,8 @@ async function handleCompleteResult( 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)) } From 0651afd60beebf6c5d1bbcf70357cd723a142597 Mon Sep 17 00:00:00 2001 From: David <60020664+dhasani23@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:07:29 -0700 Subject: [PATCH 097/153] feat(amazonq): client-side build support #6771 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem We should perform builds client-side (rather than server-side) to handle customers’ unique / challenging local environments, which often cause our server-side build to fail. ## Solution Periodically download intermediate `diff.patch` files, apply them to a copy of the project, perform the local build, then upload the build logs and resume the transformation. --- .../test/e2e/amazonq/transformByQ.test.ts | 31 ++- .../amazonqGumby/transformApiHandler.test.ts | 52 +++++ .../chat/controller/controller.ts | 132 +++++++---- .../chat/controller/messenger/messenger.ts | 88 +++++--- .../controller/messenger/messengerUtils.ts | 11 +- .../src/amazonqGumby/chat/session/session.ts | 3 +- .../commands/startTransformByQ.ts | 46 +--- .../src/codewhisperer/models/constants.ts | 16 +- .../core/src/codewhisperer/models/model.ts | 49 ++++- .../transformByQ/transformApiHandler.ts | 207 ++++++++++++++---- .../transformByQ/transformFileHandler.ts | 80 +++++-- .../transformByQ/transformMavenHandler.ts | 60 ++--- .../transformationHubViewProvider.ts | 18 +- .../transformationResultsViewProvider.ts | 8 +- packages/core/src/dev/config.ts | 3 + .../commands/transformByQ.test.ts | 78 ++++++- 16 files changed, 635 insertions(+), 247 deletions(-) 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/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/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index c1e3bd34ea3..af3f462bf95 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 = await AuthUtil.instance.getChatAuthState() if (authState.amazonQ !== '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 1f80aaf26c6..30324bab06f 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 { FeatureAuthState } from '../../../../codewhisperer/util/authUtil' @@ -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' @@ -416,38 +418,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 ) @@ -486,6 +462,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 @@ -668,7 +647,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 }) ) @@ -772,7 +751,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/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/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 9d17b166cb8..2fb3dd10069 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -599,6 +599,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 +658,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 +770,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.' 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/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/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/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) From 1dff0998004642aac45885bb188d8a3894981ba0 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Wed, 30 Apr 2025 22:16:54 -0400 Subject: [PATCH 098/153] feat(agentic chat): Add changelog (#7211) Add changelog for the agentic chat release --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../Feature-ef539952-41de-40d0-870e-94aee31f36d3.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json diff --git a/packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json b/packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json new file mode 100644 index 00000000000..64aa113d435 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf" +} From a7aff920358a06f359938cee39c3148f2a2e6459 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Thu, 1 May 2025 14:55:43 -0400 Subject: [PATCH 099/153] feat(amazonq): point to flare production manifest (#7214) ## Problem We currently point to the alpha manifest and Flare deployment is ready for us to point to prod. ## Solution - switch the config to point to prod. - supported version `1.*.*` will force us to explicitly support new major versions, but automatically pull new minor versions. Following the LSP toolkit spec. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/config.ts b/packages/amazonq/src/lsp/config.ts index c9c9b0df5ac..bb6870cb561 100644 --- a/packages/amazonq/src/lsp/config.ts +++ b/packages/amazonq/src/lsp/config.ts @@ -31,8 +31,8 @@ export function isValidConfigSection(section: unknown): section is ConfigSection } export const defaultAmazonQLspConfig: ExtendedAmazonQLSPConfig = { - manifestUrl: 'https://d3akiidp1wvqyg.cloudfront.net/qAgenticChatServer/0/manifest.json', // TODO swap this back - supportedVersions: '*', // TODO swap this back + 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, From 12c0f1c0ddf2199a5fd17791ab1cdbca05599bc8 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Thu, 1 May 2025 18:58:55 +0000 Subject: [PATCH 100/153] Release 3.57.0 --- package-lock.json | 7 ++++--- packages/toolkit/.changes/3.57.0.json | 10 ++++++++++ .../Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json | 4 ---- packages/toolkit/CHANGELOG.md | 4 ++++ packages/toolkit/package.json | 2 +- 5 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 packages/toolkit/.changes/3.57.0.json delete mode 100644 packages/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json diff --git a/package-lock.json b/package-lock.json index d09c99e0429..4a576656f32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -24694,8 +24694,9 @@ }, "node_modules/ts-node": { "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, - "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -28097,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.57.0-SNAPSHOT", + "version": "3.57.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json b/packages/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json deleted file mode 100644 index 1d7f7fe17ca..00000000000 --- a/packages/toolkit/.changes/next-release/Feature-b9357975-bda4-4ec8-a8ea-d955708b7596.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "AppBuilder: unchecking the 'Attach a debugger' checkbox in local invoke webview invokes the function without a debugger" -} diff --git a/packages/toolkit/CHANGELOG.md b/packages/toolkit/CHANGELOG.md index aa29ed25324..73ae6ed4c4c 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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 diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 65c9b4bdafb..cfc86826ff4 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.57.0-SNAPSHOT", + "version": "3.57.0", "extensionKind": [ "workspace" ], From 119806f00d3d66dbb0cd36a2f303b7bd81631cc9 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Thu, 1 May 2025 18:59:22 +0000 Subject: [PATCH 101/153] Release 1.63.0 --- package-lock.json | 7 ++++--- packages/amazonq/.changes/1.63.0.json | 14 ++++++++++++++ ...g Fix-415cbf18-1985-4e66-9918-6789133896c7.json | 4 ---- ...ature-ef539952-41de-40d0-870e-94aee31f36d3.json | 4 ---- packages/amazonq/CHANGELOG.md | 5 +++++ packages/amazonq/package.json | 2 +- 6 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 packages/amazonq/.changes/1.63.0.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json delete mode 100644 packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json diff --git a/package-lock.json b/package-lock.json index d09c99e0429..173d23a543d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -24694,8 +24694,9 @@ }, "node_modules/ts-node": { "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, - "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -26383,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.63.0-SNAPSHOT", + "version": "1.63.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json b/packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json deleted file mode 100644 index 61d1506ebe8..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-415cbf18-1985-4e66-9918-6789133896c7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Q profile selection hangs when a region is blocked" -} diff --git a/packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json b/packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json deleted file mode 100644 index 64aa113d435..00000000000 --- a/packages/amazonq/.changes/next-release/Feature-ef539952-41de-40d0-870e-94aee31f36d3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf" -} diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index 9bd9b737b06..94b3bb61cfb 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,8 @@ +## 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 diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 96b95775262..663a0bb388b 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.63.0-SNAPSHOT", + "version": "1.63.0", "extensionKind": [ "workspace" ], From 427d00a45f4736aa62e1a60070055c73e02d4e98 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Thu, 1 May 2025 19:47:53 +0000 Subject: [PATCH 102/153] Update version to snapshot version: 1.64.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/amazonq/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 173d23a543d..02ac4f6add4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.63.0", + "version": "1.64.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 663a0bb388b..47d1c8c2aa0 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.63.0", + "version": "1.64.0-SNAPSHOT", "extensionKind": [ "workspace" ], From b1d9dc14328f57d6fcfb1e5b2cb5a2e51891b76b Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Thu, 1 May 2025 19:57:38 +0000 Subject: [PATCH 103/153] Update version to snapshot version: 3.58.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/toolkit/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a576656f32..9497c2ac347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.57.0", + "version": "3.58.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index cfc86826ff4..aa59eb2f0d0 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.57.0", + "version": "3.58.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 91f584f5df44b30a7ef188403ebc9a3dbd3161d5 Mon Sep 17 00:00:00 2001 From: Lei Gao <97199248+leigaol@users.noreply.github.com> Date: Thu, 1 May 2025 14:16:47 -0700 Subject: [PATCH 104/153] fix(amazonq): enable agentic chat in AL2 (#7212) ## Problem LSP depends on node 18 which depends on GLIBC >=2.28. Amzn al2 instances do not have GLIBC >=2.28. ## Solution Use the patch of glibc if it is available. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- ...-534a4369-a0e1-4789-9f55-fa172b9fb93f.json | 4 +++ packages/amazonq/src/extension.ts | 6 ++-- packages/amazonq/src/lsp/client.ts | 34 +++++++++++++++---- packages/core/src/amazonq/lsp/lspClient.ts | 4 +-- .../core/src/shared/lsp/utils/platform.ts | 16 +++++---- 5 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json diff --git a/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json b/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json new file mode 100644 index 00000000000..59ee9d3498e --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Enable Amazon Q LSP in AL2 instances" +} diff --git a/packages/amazonq/src/extension.ts b/packages/amazonq/src/extension.ts index ffcbf333762..5ae9e397119 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -33,6 +33,7 @@ import { maybeShowMinVscodeWarning, Experiments, isSageMaker, + isAmazonInternalOs, } from 'aws-core-vscode/shared' import { ExtStartUpSources } from 'aws-core-vscode/telemetry' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' @@ -43,7 +44,7 @@ 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' +import { hasGlibcPatch } from './lsp/client' export const amazonQContextPrefix = 'amazonq' @@ -122,9 +123,10 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is await activateCodeWhisperer(extContext as ExtContext) if ( (Experiments.instance.get('amazonqLSP', true) || Auth.instance.isInternalAmazonUser()) && - !isAmazonInternalOs() + (!isAmazonInternalOs() || (await hasGlibcPatch())) ) { // start the Amazon Q LSP for internal users first + // for AL2, start LSP if glibc patch is found await activateAmazonqLsp(context) } if (!Experiments.instance.get('amazonqLSPInline', false)) { diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index d813370cb0b..40f1cabb060 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -32,6 +32,8 @@ import { getLogger, undefinedIfEmpty, getOptOutPreference, + isAmazonInternalOs, + fs, } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' @@ -40,6 +42,10 @@ import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './con const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') +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 @@ -55,19 +61,33 @@ export async function startLanguageServer( '--pre-init-encryption', '--set-credentials-encryption-key', ] - const serverOptions = createServerOptions({ - encryptionKey, - executable: resourcePaths.node, - serverModule, - execArgv: argv, - }) const documentSelector = [{ scheme: 'file', language: '*' }] const clientId = 'amazonq' 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 serverOptions = createServerOptions({ + encryptionKey, + executable: executable, + serverModule, + execArgv: argv, + }) - await validateNodeExe(resourcePaths.node, resourcePaths.lsp, argv, logger) + await validateNodeExe(executable, resourcePaths.lsp, argv, logger) // Options to control the language client const clientOptions: LanguageClientOptions = { diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index b992fef2fe3..bd671af0a39 100644 --- a/packages/core/src/amazonq/lsp/lspClient.ts +++ b/packages/core/src/amazonq/lsp/lspClient.ts @@ -255,7 +255,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths const serverOptions = createServerOptions({ encryptionKey: key, - executable: resourcePaths.node, + executable: [resourcePaths.node], serverModule, // TODO(jmkeyes): we always use the debug options...? execArgv: debugOptions.execArgv, @@ -263,7 +263,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths 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/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 5d96ef496f4..7db40ffdf30 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -29,9 +29,12 @@ 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) { @@ -41,7 +44,7 @@ export async function validateNodeExe(nodePath: string, lsp: string, args: strin } // 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))) @@ -80,16 +83,17 @@ export function createServerOptions({ execArgv, }: { encryptionKey: Buffer - executable: string + executable: string[] serverModule: string execArgv: string[] }) { 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) // this is a long running process, awaiting it will never resolve void lspProcess.run() From 68a443fd4c52384f01ba43afbd37233a4ca7f2c6 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Fri, 2 May 2025 15:28:51 -0400 Subject: [PATCH 105/153] feat(amazonq): allow child processes to define custom thresholds (#7205) ## Problem Some processes use significantly more cpu/memory than others, so we shouldn't hold them to the same static threshold. As an example, the language servers are consistently using more memory than the 100 MB threshold. Based on some experimentation with different sized workspaces, I've noticed that the process running agentic chat uses roughly 80MB-150MB of memory, and the workspace indexing uses 550 - 700 MB memory. ## Solution - Allow each child process to set a warning threshold, with a default of 100MB of memory. - Set the Q LSP threshold to 200 MB, and workspace indexing to 800 MB. - This should help to allow these logs to point to real concerns, rather than be noise. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 3 + packages/core/src/amazonq/lsp/lspClient.ts | 3 + .../core/src/shared/lsp/utils/platform.ts | 4 +- .../core/src/shared/utilities/processUtils.ts | 42 +++++++++--- .../shared/utilities/processUtils.test.ts | 64 +++++++++++++++++-- 5 files changed, 99 insertions(+), 17 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 40f1cabb060..a8cb8d76a40 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -35,6 +35,7 @@ import { isAmazonInternalOs, fs, } from 'aws-core-vscode/shared' +import { processUtils } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config' @@ -80,11 +81,13 @@ export async function startLanguageServer( executable = [resourcePaths.node] } + const memoryWarnThreshold = 1024 * processUtils.oneMB const serverOptions = createServerOptions({ encryptionKey, executable: executable, serverModule, execArgv: argv, + warnThresholds: { memory: memoryWarnThreshold }, }) await validateNodeExe(executable, resourcePaths.lsp, argv, logger) diff --git a/packages/core/src/amazonq/lsp/lspClient.ts b/packages/core/src/amazonq/lsp/lspClient.ts index bd671af0a39..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,6 +253,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths } const serverModule = resourcePaths.lsp + const memoryWarnThreshold = 800 * oneMB const serverOptions = createServerOptions({ encryptionKey: key, @@ -259,6 +261,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths serverModule, // TODO(jmkeyes): we always use the debug options...? execArgv: debugOptions.execArgv, + warnThresholds: { memory: memoryWarnThreshold }, }) const documentSelector = [{ scheme: 'file', language: '*' }] diff --git a/packages/core/src/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 7db40ffdf30..8b775433277 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -81,11 +81,13 @@ export function createServerOptions({ executable, serverModule, execArgv, + warnThresholds, }: { encryptionKey: Buffer executable: string[] serverModule: string execArgv: string[] + warnThresholds?: { cpu?: number; memory?: number } }) { return async () => { const bin = executable[0] @@ -93,7 +95,7 @@ export function createServerOptions({ if (isDebugInstance()) { args.unshift('--inspect=6080') } - const lspProcess = new ChildProcess(bin, 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/utilities/processUtils.ts b/packages/core/src/shared/utilities/processUtils.ts index 0204736f500..be44ba89bd2 100644 --- a/packages/core/src/shared/utilities/processUtils.ts +++ b/packages/core/src/shared/utilities/processUtils.ts @@ -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,10 +80,6 @@ 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() @@ -82,6 +89,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,13 +122,17 @@ 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}%`) } } } @@ -248,6 +268,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/test/shared/utilities/processUtils.test.ts b/packages/core/src/test/shared/utilities/processUtils.test.ts index 436ac48ecc4..65817d80a56 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 } } @@ -454,12 +456,12 @@ describe('ChildProcessTracker', function () { 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, + memory: defaultProcessWarnThresholds.memory + 1, } usageMock.returns(highCpu) @@ -480,7 +482,7 @@ describe('ChildProcessTracker', function () { tracker.add(runningProcess.childProcess) usageMock.returns({ - cpu: ChildProcessTracker.thresholds.cpu + 1, + cpu: defaultProcessWarnThresholds.cpu + 1, memory: 0, }) @@ -492,10 +494,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 +507,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) + }) }) From 23b164d6a4859c517b3e3993a5d4ff7ec9a3a1b7 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 2 May 2025 19:33:46 +0000 Subject: [PATCH 106/153] Release 3.58.0 --- package-lock.json | 4 ++-- packages/toolkit/.changes/3.58.0.json | 5 +++++ packages/toolkit/CHANGELOG.md | 4 ++++ packages/toolkit/package.json | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 packages/toolkit/.changes/3.58.0.json diff --git a/package-lock.json b/package-lock.json index d0e0ba3d937..407daf7c5fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.58.0-SNAPSHOT", + "version": "3.58.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/CHANGELOG.md b/packages/toolkit/CHANGELOG.md index 73ae6ed4c4c..8bceccdcf13 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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 diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index aa59eb2f0d0..1bb957956c5 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.58.0-SNAPSHOT", + "version": "3.58.0", "extensionKind": [ "workspace" ], From 0d7e5499ff2c2a674f69aa88247717b537e130f9 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 2 May 2025 19:35:32 +0000 Subject: [PATCH 107/153] Release 1.64.0 --- package-lock.json | 4 ++-- packages/amazonq/.changes/1.64.0.json | 10 ++++++++++ .../Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json | 4 ---- packages/amazonq/CHANGELOG.md | 4 ++++ packages/amazonq/package.json | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 packages/amazonq/.changes/1.64.0.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json diff --git a/package-lock.json b/package-lock.json index d0e0ba3d937..45d8ba326e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.64.0-SNAPSHOT", + "version": "1.64.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json b/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json deleted file mode 100644 index 59ee9d3498e..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-534a4369-a0e1-4789-9f55-fa172b9fb93f.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Enable Amazon Q LSP in AL2 instances" -} diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index 94b3bb61cfb..0a3613017aa 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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 diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 47d1c8c2aa0..234145bc37a 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.64.0-SNAPSHOT", + "version": "1.64.0", "extensionKind": [ "workspace" ], From 28d5b914da734e4d9063af9a5adc53759b922bf5 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Mon, 5 May 2025 16:21:33 +0000 Subject: [PATCH 108/153] Update version to snapshot version: 3.59.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/toolkit/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 407daf7c5fa..513662b8c32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.58.0", + "version": "3.59.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 1bb957956c5..806a87d097b 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.58.0", + "version": "3.59.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 0221d07f60a41efdb25a3c1a4d75a2d1fb1f1858 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Mon, 5 May 2025 16:21:37 +0000 Subject: [PATCH 109/153] Update version to snapshot version: 1.65.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/amazonq/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45d8ba326e9..8e1fa3e2f6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.64.0", + "version": "1.65.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 234145bc37a..3c487095c76 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.64.0", + "version": "1.65.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 1fb7013ec24c7f098c18038ca028e31184857c59 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Mon, 5 May 2025 09:51:11 -0700 Subject: [PATCH 110/153] feat(auth): autofill last sso login info #7223 ## Problem Customers hope we could auto-fill the url and region with the last used one. Actually there is a customer saying it would cause friction for their adoption ## Solution Memorize the url/region as a global state within VSCode and use them when they're present --- .../Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json | 4 ++++ .../src/login/webview/vue/amazonq/backend_amazonq.ts | 4 ++++ packages/core/src/login/webview/vue/backend.ts | 9 +++++++-- packages/core/src/login/webview/vue/login.vue | 10 ++++++---- .../src/login/webview/vue/toolkit/backend_toolkit.ts | 5 +++++ packages/core/src/shared/globalState.ts | 1 + 6 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json diff --git a/packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json b/packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json new file mode 100644 index 00000000000..c6324a4570f --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Memorize and autofill users' last Sso login profile" +} 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 6ed152ab4ea..a83e99d04b7 100644 --- a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts +++ b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts @@ -80,6 +80,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, diff --git a/packages/core/src/login/webview/vue/backend.ts b/packages/core/src/login/webview/vue/backend.ts index 9fc91589e91..c8f1f38d4d7 100644 --- a/packages/core/src/login/webview/vue/backend.ts +++ b/packages/core/src/login/webview/vue/backend.ts @@ -290,8 +290,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 3c2a9e2e8ec..4e4db35b9ad 100644 --- a/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts +++ b/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts @@ -23,6 +23,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' @@ -46,6 +47,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/globalState.ts b/packages/core/src/shared/globalState.ts index 6ecca56f90a..13db46b430a 100644 --- a/packages/core/src/shared/globalState.ts +++ b/packages/core/src/shared/globalState.ts @@ -69,6 +69,7 @@ export type globalKey = | 'lastSelectedRegion' | 'lastOsStartTime' | 'recentCredentials' + | 'recentSso' // List of regions enabled in AWS Explorer. | 'region' // TODO: implement this via `PromptSettings` instead of globalState. From f30f770c04a926a5be4eb40c34610a1c42539290 Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Mon, 5 May 2025 09:59:19 -0700 Subject: [PATCH 111/153] feat(amazonq): show all customizations across different profiles #7181 this effectively restores #7060 (and reverts "revert PR" #7129) --- ...-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json | 4 + .../region/regionProfileManager.test.ts | 56 ++++++++- packages/core/src/codewhisperer/activation.ts | 28 +---- .../src/codewhisperer/client/codewhisperer.ts | 26 +--- packages/core/src/codewhisperer/index.ts | 8 +- .../region/regionProfileManager.ts | 38 ++++-- .../codewhisperer/util/customizationUtil.ts | 111 ++++++++++++++++-- packages/core/src/shared/featureConfig.ts | 27 ++--- .../test/amazonq/customizationUtil.test.ts | 41 +++++++ 9 files changed, 249 insertions(+), 90 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json diff --git a/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json b/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json new file mode 100644 index 00000000000..d3838c1b4d6 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Support selecting customizations across all Q profiles with automatic profile switching for enterprise users" +} diff --git a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index af79f7dc2e5..11441b9bf6f 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -65,7 +65,7 @@ describe('RegionProfileManager', function () { const mockClient = { listAvailableProfiles: listProfilesStub, } - const createClientStub = sinon.stub(sut, 'createQClient').resolves(mockClient) + const createClientStub = sinon.stub(sut, '_createQClient').resolves(mockClient) const r = await sut.listRegionProfile() @@ -234,13 +234,65 @@ describe('RegionProfileManager', function () { }) describe('createQClient', function () { + it(`should configure the endpoint and region from a profile`, async function () { + await setupConnection('idc') + + const iadClient = await sut.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 sut.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 sut.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 sut.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 sut.switchRegionProfile(profileFoo, 'user') assert.deepStrictEqual(sut.activeRegionProfile, profileFoo) const conn = authUtil.conn as SsoConnection - const client = await sut.createQClient('eu-central-1', 'https://amazon.com/', conn) + const client = await sut._createQClient('eu-central-1', 'https://amazon.com/', conn) assert.deepStrictEqual(client.config.region, 'eu-central-1') assert.deepStrictEqual(client.endpoint.href, 'https://amazon.com/') diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index b0aa54e17a0..d3473bd649f 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' @@ -344,26 +339,7 @@ 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 - ) - }) - } - }) + auth.regionProfileManager.onDidChangeRegionProfile(onProfileChangedListener) ) // run the auth startup code with context for telemetry diff --git a/packages/core/src/codewhisperer/client/codewhisperer.ts b/packages/core/src/codewhisperer/client/codewhisperer.ts index 2412f6922a8..35f699b24c2 100644 --- a/packages/core/src/codewhisperer/client/codewhisperer.ts +++ b/packages/core/src/codewhisperer/client/codewhisperer.ts @@ -7,19 +7,17 @@ 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 { isSsoConnection } from '../../auth/connection' -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' @@ -219,28 +217,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/index.ts b/packages/core/src/codewhisperer/index.ts index 930b168beec..98b7f9239b1 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -99,7 +99,13 @@ 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/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index aa51d163262..149a78391f8 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -49,12 +49,17 @@ 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 @@ -141,7 +146,7 @@ export class RegionProfileManager { const failedRegions: string[] = [] for (const [region, endpoint] of endpoints.entries()) { - const client = await this.createQClient(region, endpoint, conn as SsoConnection) + const client = await this._createQClient(region, endpoint, conn as SsoConnection) const requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) => client.listAvailableProfiles(request).promise() const request: CodeWhispererUserClient.ListAvailableProfilesRequest = {} @@ -195,7 +200,7 @@ export class RegionProfileManager { const ssoConn = this.connectionProvider() as SsoConnection // 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', @@ -237,13 +242,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() @@ -384,7 +392,21 @@ export class RegionProfileManager { await this.cache.clearCache() } - async createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { + // TODO: Should maintain sdk client in a better way + async createQClient(profile: RegionProfile): Promise { + const conn = this.connectionProvider() + if (conn === undefined || !isSsoConnection(conn)) { + 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, conn) + } + + // Visible for testing only, do not use this directly, please use createQClient(profile) + async _createQClient(region: string, endpoint: string, conn: SsoConnection): Promise { const token = (await conn.getToken()).accessToken const serviceOption: ServiceOptions = { apiConfig: userApiConfig, diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index d2bcc8ce703..600317c53e0 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() + } + } +} /** * @@ -227,7 +290,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', @@ -286,8 +348,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 ) => { @@ -296,8 +362,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) @@ -306,6 +372,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: @@ -336,13 +406,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/shared/featureConfig.ts b/packages/core/src/shared/featureConfig.ts index 6d59fe0782a..d7acb9657be 100644 --- a/packages/core/src/shared/featureConfig.ts +++ b/packages/core/src/shared/featureConfig.ts @@ -4,7 +4,6 @@ */ import { - Customization, FeatureValue, ListFeatureEvaluationsRequest, ListFeatureEvaluationsResponse, @@ -21,7 +20,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() @@ -153,19 +152,7 @@ export class FeatureConfigProvider { if (isBuilderIdConnection(AuthUtil.instance.conn)) { this.featureConfigs.delete(Features.customizationArnOverride) } else if (isIdcSsoConnection(AuthUtil.instance.conn)) { - 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) @@ -176,6 +163,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/test/amazonq/customizationUtil.test.ts b/packages/core/src/test/amazonq/customizationUtil.test.ts index 505c89ae0c9..a3a49e907d9 100644 --- a/packages/core/src/test/amazonq/customizationUtil.test.ts +++ b/packages/core/src/test/amazonq/customizationUtil.test.ts @@ -11,9 +11,11 @@ import { AuthUtil, baseCustomization, Customization, + CustomizationProvider, FeatureConfigProvider, getSelectedCustomization, refreshStatusBar, + RegionProfileManager, setSelectedCustomization, } from '../../codewhisperer' import { FeatureContext, globals } from '../../shared' @@ -23,6 +25,45 @@ import { SsoConnection } from '../../auth' const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' +describe('customizationProvider', function () { + let auth: ReturnType + let ssoConn: SsoConnection + let regionProfileManager: RegionProfileManager + + beforeEach(async () => { + auth = createTestAuth(globals.globalState) + ssoConn = await auth.createInvalidSsoConnection( + createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) + ) + + regionProfileManager = new RegionProfileManager(() => ssoConn) + }) + + 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 ssoConn: SsoConnection From f1f5a362a902959dd8366c2c015c04e66297fc00 Mon Sep 17 00:00:00 2001 From: Tom Zu <138054255+tomcat323@users.noreply.github.com> Date: Mon, 5 May 2025 14:03:08 -0400 Subject: [PATCH 112/153] feat(nep): Data Instrumentation (#7109) ## Problem The science & service team in Next Edit Prediction (NEP) project needs initial data for model training and tuning, to provide more contextually relevant suggestions. This requires us to track user edits in the IDE, and send changes (of current active file in unified diff format) to the codeWhisper API. **No impact on user experience.** **This won't live forever, will be migrated to flare by end of May, needed now for science data collection.** ## Solution ### Key Components `PredictionKeyStrokeHandler`: Listens for document changes, maintains shadow copies of visible documents, and processes edits. `PredictionTracker`: Manages file snapshots, implementing a policy for storing, retrieving, and pruning snapshots based on age and memory constraints. `DiffGenerator`: Creates unified diffs between file snapshots, produces `supplementalContext` sent to the API. ### How it Works - The system track shadow copies of editor visible files' content - Once an edit is made to a tracked file, it takes a snapshot of the file content before the edit - When the Inline API fires, the snapshots of the current editing files are used to generate diff context ### Memory management - maxTotalSizeKb (default: 5000): Caps total size of snapshots storage at ~5MB, purging oldest snapshots when exceeded. - debounceIntervalMs (default: 2000): Prevents excessive snapshots by requiring 2 seconds between captures for the same file. - maxAgeMs (default: 30000): Auto-deletes snapshots after 30 seconds to maintain recent-only history. - maxSupplementalContext (default: 15): Limits `supplementalContext` sent to API to 15 entries maximum. ### Changes - Added new NextEditPrediction module in the CodeWhisperer package - Updated activation code to initialize the NEP system - Updated codeWhisper inline API requests to fit new format --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/core/src/codewhisperer/activation.ts | 3 + .../src/codewhisperer/models/constants.ts | 7 + .../nextEditPrediction/activation.ts | 32 ++ .../diffContextGenerator.ts | 154 ++++++++ .../predictionKeyStrokeHandler.ts | 117 ++++++ .../nextEditPrediction/predictionTracker.ts | 236 ++++++++++++ .../service/recommendationHandler.ts | 2 +- .../src/codewhisperer/util/editorContext.ts | 40 +- packages/core/src/shared/logger/logger.ts | 1 + .../predictionTracker.test.ts | 344 ++++++++++++++++++ 10 files changed, 933 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/codewhisperer/nextEditPrediction/activation.ts create mode 100644 packages/core/src/codewhisperer/nextEditPrediction/diffContextGenerator.ts create mode 100644 packages/core/src/codewhisperer/nextEditPrediction/predictionKeyStrokeHandler.ts create mode 100644 packages/core/src/codewhisperer/nextEditPrediction/predictionTracker.ts create mode 100644 packages/core/src/test/codewhisperer/nextEditPrediction/predictionTracker.test.ts diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index d3473bd649f..e52e08bb98b 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -90,6 +90,7 @@ 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 @@ -505,6 +506,8 @@ export async function activate(context: ExtContext): Promise { }) ) } + + activateEditTracking(context) } export async function shutdown() { diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 2fb3dd10069..dc0426376ce 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -945,3 +945,10 @@ export const testGenExcludePatterns = [ '**/*.deb', '**/*.model', ] + +export const predictionTrackerDefaultConfig = { + maxStorageSizeKb: 5000, + debounceIntervalMs: 2000, + maxAgeMs: 30000, + maxSupplementalContext: 15, +} 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/service/recommendationHandler.ts b/packages/core/src/codewhisperer/service/recommendationHandler.ts index 00d1f3254a5..8ab491b32e0 100644 --- a/packages/core/src/codewhisperer/service/recommendationHandler.ts +++ b/packages/core/src/codewhisperer/service/recommendationHandler.ts @@ -328,7 +328,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/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts index 88c3d3847f1..0861b982d13 100644 --- a/packages/core/src/codewhisperer/util/editorContext.ts +++ b/packages/core/src/codewhisperer/util/editorContext.ts @@ -20,6 +20,7 @@ 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() @@ -119,8 +120,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 +135,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 +146,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 +214,29 @@ export function getTabSize(): number { return tabSize } +export function getEditorState(editor: vscode.TextEditor, fileContext: codewhispererClient.FileContext): any { + try { + return { + document: { + programmingLanguage: { + languageName: fileContext.programmingLanguage.languageName, + }, + relativeFilePath: fileContext.filename, + text: editor.document.getText(), + }, + 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/shared/logger/logger.ts b/packages/core/src/shared/logger/logger.ts index 85df7b4e1f8..38ff40627c1 100644 --- a/packages/core/src/shared/logger/logger.ts +++ b/packages/core/src/shared/logger/logger.ts @@ -18,6 +18,7 @@ export type LogTopic = | 'chat' | 'stepfunctions' | 'unknown' + | 'nextEditPrediction' | 'resourceCache' class ErrorLog { 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)) + }) + }) +}) From 10ff228ea3d0c898f28dd84cc998e289cd7476f0 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Mon, 5 May 2025 18:07:42 +0000 Subject: [PATCH 113/153] Release 3.59.0 --- package-lock.json | 4 ++-- packages/toolkit/.changes/3.59.0.json | 5 +++++ packages/toolkit/CHANGELOG.md | 4 ++++ packages/toolkit/package.json | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 packages/toolkit/.changes/3.59.0.json diff --git a/package-lock.json b/package-lock.json index 8511e57e140..dd4508b1b60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.59.0-SNAPSHOT", + "version": "3.59.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/CHANGELOG.md b/packages/toolkit/CHANGELOG.md index 8bceccdcf13..215c7c68cba 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.59.0 2025-05-05 + +- Miscellaneous non-user-facing changes + ## 3.58.0 2025-05-02 - Miscellaneous non-user-facing changes diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 806a87d097b..40e1790327e 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.59.0-SNAPSHOT", + "version": "3.59.0", "extensionKind": [ "workspace" ], From f898e9314df245c865cc262a28bf7cea37be5bb1 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Mon, 5 May 2025 18:07:44 +0000 Subject: [PATCH 114/153] Release 1.65.0 --- package-lock.json | 4 ++-- packages/amazonq/.changes/1.65.0.json | 14 ++++++++++++++ ...ature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json | 4 ---- ...ature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json | 4 ---- packages/amazonq/CHANGELOG.md | 5 +++++ packages/amazonq/package.json | 2 +- 6 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 packages/amazonq/.changes/1.65.0.json delete mode 100644 packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json delete mode 100644 packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json diff --git a/package-lock.json b/package-lock.json index 8511e57e140..743c3c1e016 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.65.0-SNAPSHOT", + "version": "1.65.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json b/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json deleted file mode 100644 index d3838c1b4d6..00000000000 --- a/packages/amazonq/.changes/next-release/Feature-2a696d00-e8c8-44a4-ab2c-2204b4d8e31d.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "Support selecting customizations across all Q profiles with automatic profile switching for enterprise users" -} diff --git a/packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json b/packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json deleted file mode 100644 index c6324a4570f..00000000000 --- a/packages/amazonq/.changes/next-release/Feature-3fb0a20d-75e8-4141-a1a6-d8cfd9b7f3f0.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "Memorize and autofill users' last Sso login profile" -} diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index 0a3613017aa..b5ceba33c7c 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,8 @@ +## 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 diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 3c487095c76..f44f9fcc857 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.65.0-SNAPSHOT", + "version": "1.65.0", "extensionKind": [ "workspace" ], From 0363ba43802c20a5d16a3f52e9196a647d412a26 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Tue, 6 May 2025 09:01:42 -0400 Subject: [PATCH 115/153] fix(amazonq): Merge master into feature/amazonqLSP-auth (#7227) ## Problem The code changes that originated from the feature/hybridChat branch are not merged in into feature/amazonqLSP-auth yet ## Solution Merge master into feature/amazonqLSP-auth **Note: This is the first PR of the merge, which just fixes most conflicts. A follow-up PR will address bugs and logic changes** --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Signed-off-by: nkomonen-amazon Co-authored-by: aws-toolkit-automation <43144436+aws-toolkit-automation@users.noreply.github.com> Co-authored-by: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Co-authored-by: Josh Pinkney Co-authored-by: Justin M. Keyes Co-authored-by: Lei Gao <97199248+leigaol@users.noreply.github.com> Co-authored-by: aws-toolkit-automation <> Co-authored-by: zuoyaofu Co-authored-by: aditya169 Co-authored-by: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Co-authored-by: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Co-authored-by: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Co-authored-by: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Co-authored-by: Tai Lai Co-authored-by: chungjac Co-authored-by: Dogus Atasoy <116281103+dogusata@users.noreply.github.com> Co-authored-by: Avi Alpert <131792194+avi-alpert@users.noreply.github.com> Co-authored-by: nkomonen-amazon Co-authored-by: Frederic Mbea <117131783+mbfreder@users.noreply.github.com> Co-authored-by: Tom Zu <138054255+tomcat323@users.noreply.github.com> Co-authored-by: David <60020664+dhasani23@users.noreply.github.com> --- docs/lsp.md | 8 +- package-lock.json | 1521 ++++++----------- package.json | 2 +- packages/amazonq/.changes/1.61.0.json | 14 + packages/amazonq/.changes/1.62.0.json | 14 + packages/amazonq/.changes/1.63.0.json | 14 + packages/amazonq/.changes/1.64.0.json | 10 + packages/amazonq/.vscode/launch.json | 3 +- packages/amazonq/CHANGELOG.md | 19 + packages/amazonq/package.json | 30 +- packages/amazonq/src/app/chat/activation.ts | 31 +- .../src/app/chat/node/activateAgents.ts | 19 + packages/amazonq/src/extension.ts | 19 +- packages/amazonq/src/extensionNode.ts | 27 +- packages/amazonq/src/lsp/chat/activation.ts | 110 +- packages/amazonq/src/lsp/chat/commands.ts | 53 + packages/amazonq/src/lsp/chat/error.ts | 23 + packages/amazonq/src/lsp/chat/messages.ts | 224 ++- .../amazonq/src/lsp/chat/webviewProvider.ts | 137 +- packages/amazonq/src/lsp/client.ts | 276 ++- packages/amazonq/src/lsp/config.ts | 34 +- packages/amazonq/src/lsp/lspInstaller.ts | 2 + .../test/e2e/amazonq/transformByQ.test.ts | 31 +- .../amazonq/test/e2e/inline/inline.test.ts | 2 +- .../test/unit/amazonq/lsp/chat/error.test.ts | 15 + .../amazonqGumby/transformApiHandler.test.ts | 52 + .../region/regionProfileManager.test.ts | 4 +- packages/core/package.json | 10 +- packages/core/package.nls.json | 5 +- .../commons/controllers/contentController.ts | 33 +- .../controllers/diffContentProvider.ts | 51 + packages/core/src/amazonq/commons/model.ts | 10 + packages/core/src/amazonq/index.ts | 10 +- packages/core/src/amazonq/indexNode.ts | 13 + packages/core/src/amazonq/lsp/lspClient.ts | 7 +- .../webview/generators/featureConfig.ts | 35 + .../webview/generators/webViewContent.ts | 35 +- .../webview/messages/messageDispatcher.ts | 169 +- .../amazonq/webview/ui/connectorAdapter.ts | 98 ++ packages/core/src/amazonq/webview/ui/main.ts | 34 +- .../webview/ui/quickActions/handler.ts | 84 +- .../core/src/amazonqDoc/session/session.ts | 2 +- .../core/src/amazonqFeatureDev/constants.ts | 8 - .../src/amazonqFeatureDev/session/session.ts | 4 +- .../chat/controller/controller.ts | 132 +- .../chat/controller/messenger/messenger.ts | 88 +- .../controller/messenger/messengerUtils.ts | 11 +- .../src/amazonqGumby/chat/session/session.ts | 3 +- packages/core/src/auth/sso/clients.ts | 4 +- packages/core/src/codewhisperer/activation.ts | 29 +- .../commands/startTransformByQ.ts | 46 +- .../core/src/codewhisperer/commands/types.ts | 3 + .../src/codewhisperer/models/constants.ts | 16 +- .../core/src/codewhisperer/models/model.ts | 49 +- .../region/regionProfileManager.ts | 54 +- .../core/src/codewhisperer/region/utils.ts | 49 + .../service/inlineCompletionService.ts | 9 + .../transformByQ/transformApiHandler.ts | 207 ++- .../transformByQ/transformFileHandler.ts | 80 +- .../transformByQ/transformMavenHandler.ts | 60 +- .../transformationHubViewProvider.ts | 18 +- .../transformationResultsViewProvider.ts | 8 +- .../codewhisperer/ui/codeWhispererNodes.ts | 15 +- .../src/codewhisperer/ui/statusBarMenu.ts | 9 +- .../core/src/codewhisperer/util/authUtil.ts | 7 +- .../util/codewhispererSettings.ts | 18 +- .../codewhisperer/util/customizationUtil.ts | 3 + packages/core/src/codewhispererChat/app.ts | 2 +- .../commands/registerCommands.ts | 73 +- packages/core/src/dev/config.ts | 3 + .../vue/configEditor/samInvokeComponent.vue | 12 + .../vue/configEditor/samInvokeFrontend.ts | 3 + .../webview/vue/amazonq/backend_amazonq.ts | 2 +- packages/core/src/shared/extensionGlobals.ts | 4 +- packages/core/src/shared/globalState.ts | 1 + packages/core/src/shared/index.ts | 3 +- packages/core/src/shared/logger/logger.ts | 6 +- .../core/src/shared/lsp/baseLspInstaller.ts | 17 +- packages/core/src/shared/lsp/lspResolver.ts | 41 +- packages/core/src/shared/lsp/types.ts | 4 + packages/core/src/shared/lsp/utils/cleanup.ts | 15 +- .../core/src/shared/lsp/utils/platform.ts | 18 +- .../core/src/shared/settings-amazonq.gen.ts | 7 +- .../core/src/shared/settings-toolkit.gen.ts | 1 + .../src/shared/telemetry/vscodeTelemetry.json | 55 - .../src/shared/utilities/collectionUtils.ts | 21 +- .../core/src/shared/utilities/messages.ts | 2 +- .../core/src/shared/utilities/processUtils.ts | 42 +- .../src/shared/utilities/resourceCache.ts | 190 ++ .../shared/utilities/textDocumentUtilities.ts | 75 +- .../src/shared/utilities/textUtilities.ts | 8 +- packages/core/src/shared/vscode/commands2.ts | 2 +- .../controllers/contentController.test.ts | 143 ++ .../commands/transformByQ.test.ts | 78 +- .../src/test/shared/lsp/utils/cleanup.test.ts | 8 +- .../shared/utilities/collectionUtils.test.ts | 91 +- .../shared/utilities/processUtils.test.ts | 64 +- .../utilities/textDocumentUtilities.test.ts | 140 ++ packages/core/webpack.config.js | 1 + packages/toolkit/.changes/3.56.0.json | 5 + packages/toolkit/.changes/3.57.0.json | 10 + packages/toolkit/.changes/3.58.0.json | 5 + packages/toolkit/CHANGELOG.md | 12 + packages/toolkit/package.json | 8 +- 104 files changed, 3521 insertions(+), 1871 deletions(-) create mode 100644 packages/amazonq/.changes/1.61.0.json create mode 100644 packages/amazonq/.changes/1.62.0.json create mode 100644 packages/amazonq/.changes/1.63.0.json create mode 100644 packages/amazonq/.changes/1.64.0.json create mode 100644 packages/amazonq/src/app/chat/node/activateAgents.ts create mode 100644 packages/amazonq/src/lsp/chat/error.ts create mode 100644 packages/amazonq/test/unit/amazonq/lsp/chat/error.test.ts create mode 100644 packages/core/src/amazonq/commons/controllers/diffContentProvider.ts create mode 100644 packages/core/src/amazonq/indexNode.ts create mode 100644 packages/core/src/amazonq/webview/generators/featureConfig.ts create mode 100644 packages/core/src/amazonq/webview/ui/connectorAdapter.ts create mode 100644 packages/core/src/codewhisperer/region/utils.ts create mode 100644 packages/core/src/shared/utilities/resourceCache.ts create mode 100644 packages/core/src/test/amazonq/commons/controllers/contentController.test.ts create mode 100644 packages/core/src/test/shared/utilities/textDocumentUtilities.test.ts create mode 100644 packages/toolkit/.changes/3.56.0.json create mode 100644 packages/toolkit/.changes/3.57.0.json create mode 100644 packages/toolkit/.changes/3.58.0.json 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..8511e57e140 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.317", "@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,9 +10760,9 @@ } }, "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.317", + "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.317.tgz", + "integrity": "sha512-QFLBFfHZjuB2pBd1p0Tn/GMKTYYQu3/nrlj0Co7EkqozvDNDG0nTjxtkXxotbwjrqVD5Sv8i46gEdgsyQ7at3w==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -10821,25 +10775,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 +10817,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 +10829,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 +10841,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 +10890,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 +10938,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 +10959,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 +10974,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 +10994,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 +11016,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 +11032,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 +11050,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 +11064,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 +11077,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 +11091,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 +11108,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 +11124,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 +11140,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 +11152,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 +11166,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 +11177,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 +11200,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 +11212,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 +11227,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 +11245,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 +11260,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 +11275,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 +11289,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 +11301,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 +11312,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 +11325,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 +11343,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 +11362,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 +11374,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 +11386,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 +11400,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 +11415,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 +11427,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 +11439,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 +11452,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 +11464,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 +11475,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 +11487,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 +11505,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 +11522,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 +11533,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 +11546,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 +11559,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 +11570,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 +11581,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 +11593,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 +11604,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 +11619,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 +11636,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 +11649,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 +11660,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 +11672,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 +11685,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 +11703,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 +11714,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 +11726,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 +11740,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 +11773,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 +12111,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 +12123,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 +12315,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 +12395,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 +12403,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 +12414,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 +12425,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 +12439,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 +12447,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 +12466,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 +12484,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 +12504,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 +12525,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 +12543,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 +12562,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 +12578,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 +12598,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 +12616,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 +12634,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 +12651,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 +12659,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 +12678,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 +12693,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 +12710,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 +12730,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 +12744,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 +12758,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 +12773,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 +12781,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 +12797,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 +12812,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 +12845,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 +12853,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 +12869,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 +12877,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 +12896,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 +12935,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 +12964,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 +14118,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 +14292,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 +14314,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 +14446,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 +15882,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 +16603,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 +18569,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 +19345,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 +19637,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 +19821,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 +19875,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 +20829,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 +20870,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 +20905,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 +21487,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 +22236,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 +22609,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 +22652,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 +23172,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 +23185,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 +23201,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 +23211,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 +23389,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 +23785,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 +24872,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 +24940,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 +25220,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 +25910,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 +25922,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 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.61.0-SNAPSHOT", + "version": "1.65.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" @@ -26737,7 +26427,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 +26482,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 +26661,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 +26679,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 +26693,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 +26704,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 +26714,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 +26737,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 +26755,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 +26769,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 +26780,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 +26790,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 +26911,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 +26929,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 +26943,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 +26954,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 +26964,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 +26987,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 +27005,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 +27019,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 +27030,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 +27040,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 +27112,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 +27130,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 +27144,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 +27155,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 +27165,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 +27197,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 +27238,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 +27252,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 +27287,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 +27319,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 +27347,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 +27388,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 +27424,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 +27451,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 +27478,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 +27521,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 +27577,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 +27591,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 +27601,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 +27613,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 +27623,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 +27635,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 +27646,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 +27667,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 +27685,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 +27702,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 +27712,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 +27729,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 +27740,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 +27751,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 +27764,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 +27775,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 +27791,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 +27801,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 +27813,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 +27825,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 +27835,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 +27846,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 +27857,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 +27874,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 +27885,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 +27899,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 +27910,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 +27922,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 +27932,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 +27943,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 +27953,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 +27964,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 +27974,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 +27984,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 +27994,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 +28005,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 +28026,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 +28036,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 +28048,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 +28058,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 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.56.0-SNAPSHOT", + "version": "3.59.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" @@ -28638,6 +28177,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 +28296,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 +28343,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 +28363,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 +28377,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 +28396,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 +28418,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 +28439,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 +28454,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 +28471,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 +28486,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 +28499,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 +28511,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 +28524,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 +28540,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 +28587,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 +28602,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 +28617,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 +28628,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 +28640,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 +28661,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 +28682,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 +28693,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 +28704,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 +28718,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 +28735,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 +28749,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 +28762,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 +28774,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 +28785,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 +28797,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 +28809,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 +28823,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 +28836,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 +28848,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 +28857,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 +28869,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 +28886,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 +28904,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 +28915,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 +28926,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 +28939,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 +28953,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 +28964,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 +28975,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 +28987,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 +28998,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 +29019,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 +29036,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 +29052,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 +29062,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 +29075,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 +29087,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 +29097,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 +29107,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 +29118,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 +29127,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 +29141,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 +29157,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 +29170,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 +29179,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 +29190,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 +29202,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 +29220,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 +29230,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 +29239,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 +29283,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 +29403,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 +29452,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 +29495,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 +29528,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 +29582,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 +29610,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 +29660,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 +29715,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 +29738,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 +29761,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 +29901,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 +30020,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 +30040,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 +30053,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 +30065,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 +30078,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 +30094,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 +30141,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 +30156,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 +30171,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 +30182,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 +30194,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 +30215,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 +30236,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 +30247,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 +30258,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 +30272,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 +30289,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 +30303,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 +30316,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 +30328,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 +30339,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 +30351,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 +30363,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 +30377,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 +30390,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 +30402,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 +30411,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 +30423,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 +30440,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 +30458,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 +30469,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 +30480,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 +30493,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 +30507,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 +30518,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 +30529,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 +30541,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 +30552,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 +30573,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 +30590,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 +30606,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 +30616,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 +30629,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 +30641,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 +30651,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 +30661,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 +30672,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 +30681,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 +30695,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 +30711,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 +30724,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 +30733,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 +30744,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 +30756,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 +30774,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 +30784,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 +30793,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 +30957,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 +31006,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 +31049,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 +31082,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 +31136,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 +31164,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 +31214,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 +31269,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 +31292,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 +31315,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..30f0497cdb2 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.317", "@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/.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..0a3613017aa 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,22 @@ +## 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..3c487095c76 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.65.0-SNAPSHOT", "extensionKind": [ "workspace" ], @@ -131,6 +131,14 @@ "amazonQChatDisclaimer": { "type": "boolean", "default": false + }, + "amazonQChatPairProgramming": { + "type": "boolean", + "default": false + }, + "amazonQSelectDeveloperProfile": { + "type": "boolean", + "default": false } }, "additionalProperties": false @@ -177,7 +185,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..2b5f01ee23c 100644 --- a/packages/amazonq/src/app/chat/activation.ts +++ b/packages/amazonq/src/app/chat/activation.ts @@ -4,46 +4,32 @@ */ 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.focusAmazonQPanel.register(), + amazonq.focusAmazonQPanelKeybinding.register(), amazonq.tryChatCodeLensCommand.register(), vscode.workspace.onDidChangeConfiguration(async (configurationChangeEvent) => { if (configurationChangeEvent.affectsConfiguration('amazonQ.workspaceIndex')) { @@ -62,15 +48,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..d489f694d34 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -4,7 +4,12 @@ */ import { CredentialsStore, LoginManager, authUtils, initializeAuth } from 'aws-core-vscode/auth' -import { activate as activateCodeWhisperer, shutdown as shutdownCodeWhisperer } from 'aws-core-vscode/codewhisperer' +import { Auth, AuthUtils, CredentialsStore, LoginManager, initializeAuth } from 'aws-core-vscode/auth' +import { + activate as activateCodeWhisperer, + AuthUtil, + shutdown as shutdownCodeWhisperer, +} from 'aws-core-vscode/codewhisperer' import { makeEndpointsProvider, registerGenericCommands } from 'aws-core-vscode' import { CommonAuthWebview } from 'aws-core-vscode/login' import { @@ -34,6 +39,7 @@ import { Experiments, isSageMaker, Commands, + isAmazonInternalOs, } from 'aws-core-vscode/shared' import { ExtStartUpSources } from 'aws-core-vscode/telemetry' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' @@ -45,6 +51,7 @@ 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' +import { hasGlibcPatch } from './lsp/client' export const amazonQContextPrefix = 'amazonq' @@ -120,11 +127,17 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is extensionContext: context, } - await activateAmazonqLsp(context) - // This contains every lsp agnostic things (auth, security scan, code scan) await activateCodeWhisperer(extContext as ExtContext) + if ( + (Experiments.instance.get('amazonqLSP', true) || AuthUtil.instance.isInternalAmazonUser()) && + (!isAmazonInternalOs() || (await hasGlibcPatch())) + ) { + // start the Amazon Q LSP for internal users first + // for AL2, start LSP if glibc patch is found + await activateAmazonqLsp(context) + } if (!Experiments.instance.get('amazonqLSPInline', false)) { await activateInlineCompletion() } diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index f11a3e14f1d..98be79c1920 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' @@ -22,7 +22,8 @@ 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,10 +46,26 @@ 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()) diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index 406b753716f..9dd1d31c3de 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -8,12 +8,27 @@ 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, + }) -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 +44,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..81dd9e13515 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, @@ -37,14 +41,33 @@ import { 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 +77,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 +89,49 @@ 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) { + 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 +186,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 +320,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) @@ -315,6 +446,41 @@ export function registerMessageListeners( 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 +524,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 +538,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 +546,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..1a513f1df3f 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..5f6f548b951 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 { InlineCompletionManager } from '../app/inline/completion' -import { AuthUtil } from 'aws-core-vscode/codewhisperer' +import { LanguageClient, LanguageClientOptions, RequestType, State } from 'vscode-languageclient' import { - ConnectionMetadata, CreateFilesParams, DeleteFilesParams, DidChangeWorkspaceFoldersParams, @@ -30,10 +28,11 @@ import { ShowDocumentResult, ShowMessageRequest, ShowMessageRequestParams, + ConnectionMetadata, } from '@aws/language-server-runtimes/protocol' +import { AuthUtil, CodeWhispererSettings, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' import { Settings, - oidcClientName, createServerOptions, globals, Experiments, @@ -41,10 +40,17 @@ import { openUrl, validateNodeExe, getLogger, + undefinedIfEmpty, + getOptOutPreference, + isAmazonInternalOs, + fs, + oidcClientName, } from 'aws-core-vscode/shared' +import { processUtils } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' import { auth2 } from 'aws-core-vscode/auth' +import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config' const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') @@ -53,6 +59,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 +78,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(), + name: 'AmazonQ-For-VSCode', version: '0.0.1', }, clientId: crypto.randomUUID(), }, awsClientCapabilities: { + q: { + developerProfiles: true, + }, window: { notifications: true, + showSaveFileDialog: true, }, q: { developerProfiles: true, }, }, + logLevel: toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel), }, credentials: { providesBearerToken: true, @@ -155,6 +201,23 @@ export async function startLanguageServer( } ) + 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 +237,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,69 +260,9 @@ 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') }) - ) } @@ -284,5 +270,121 @@ export async function startLanguageServer( activate(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 + } + + // 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(), + }, + ] + 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/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/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/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index 1429618b6e4..9a4bed381d1 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -61,7 +61,7 @@ describe('RegionProfileManager', function () { } const createClientStub = sinon.stub(regionProfileManager, 'createQClient').resolves(mockClient) - const r = await regionProfileManager.listRegionProfiles() + const r = await regionProfileManager.listRegionProfile() assert.strictEqual(r.length, 2) assert.deepStrictEqual(r, [ @@ -177,7 +177,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') 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..70744417451 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,11 @@ 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 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) {} @@ -155,27 +160,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/handler.ts b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts index 85c96a1a39c..fe124d1fc0c 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,33 @@ 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, + }) + } + + 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/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/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..6eef0fffcd3 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -95,6 +95,7 @@ import { SecurityIssueTreeViewProvider } from './service/securityIssueTreeViewPr import { setContext } from '../shared/vscode/setContext' import { syncSecurityIssueWebview } from './views/securityIssue/securityIssueWebview' import { detectCommentAboveLine } from '../shared/utilities/commentUtils' +import { notifySelectDeveloperProfile } from './region/utils' let localize: nls.LocalizeFunc @@ -104,8 +105,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 +155,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 +170,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,7 +340,7 @@ export async function activate(context: ExtContext): Promise { SecurityIssueCodeActionProvider.instance ), vscode.commands.registerCommand('aws.amazonq.openEditorAtRange', openEditorAtRange), - auth.regionProfileManager.onDidChangeRegionProfile(() => { + AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => { // Validate user still has access to the selected customization. const selectedCustomization = getSelectedCustomization() // No need to validate base customization which has empty arn. @@ -366,23 +365,27 @@ export async function activate(context: ExtContext): Promise { // 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 +400,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) 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/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 9d17b166cb8..2fb3dd10069 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -599,6 +599,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 +658,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 +770,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.' 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/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index 3ad74f22d51..d4aa3b5abd8 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 = { @@ -51,7 +53,26 @@ export class RegionProfileManager { // 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,13 +115,21 @@ 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 requester = async (request: CodeWhispererUserClient.ListAvailableProfilesRequest) => @@ -125,13 +154,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 @@ -204,6 +237,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 +255,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 +315,7 @@ export class RegionProfileManager { const selected = this.activeRegionProfile let profiles: RegionProfile[] = [] try { - profiles = await this.listRegionProfiles() + profiles = await this.getProfiles() } catch (e) { return [ { @@ -333,6 +369,10 @@ export class RegionProfileManager { return this.authProvider.isIdcConnection() && this.activeRegionProfile === undefined } + async clearCache() { + await this.cache.clearCache() + } + async createQClient(region: string, endpoint: string): Promise { const token = await this.authProvider.getToken() const serviceOption: ServiceOptions = { 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/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..5ff9a1c9a55 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' @@ -146,6 +146,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 +251,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/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..22c6f57130f 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -133,6 +133,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[] => { 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/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/vue/amazonq/backend_amazonq.ts b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts index fd558e546d6..01a6c1279d7 100644 --- a/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts +++ b/packages/core/src/login/webview/vue/amazonq/backend_amazonq.ts @@ -211,7 +211,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/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/globalState.ts b/packages/core/src/shared/globalState.ts index 23a67667d88..036e7cfaf22 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' 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..85df7b4e1f8 100644 --- a/packages/core/src/shared/logger/logger.ts +++ b/packages/core/src/shared/logger/logger.ts @@ -18,6 +18,7 @@ export type LogTopic = | 'chat' | 'stepfunctions' | 'unknown' + | 'resourceCache' class ErrorLog { constructor( @@ -104,11 +105,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..27e19df6b3a 100644 --- a/packages/core/src/shared/lsp/baseLspInstaller.ts +++ b/packages/core/src/shared/lsp/baseLspInstaller.ts @@ -41,17 +41,22 @@ export abstract class BaseLspInstaller 0) { this.logger.debug(`cleaning old LSP versions: deleted ${deletedVersions.length} versions`) } @@ -71,6 +76,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..04527d67246 100644 --- a/packages/core/src/shared/lsp/lspResolver.ts +++ b/packages/core/src/shared/lsp/lspResolver.ts @@ -13,19 +13,30 @@ 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, + /** + * 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: @@ -104,7 +115,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,7 +135,7 @@ 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, @@ -217,8 +236,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 +273,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/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..8b775433277 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -29,9 +29,12 @@ 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) { @@ -41,7 +44,7 @@ export async function validateNodeExe(nodePath: string, lsp: string, args: strin } // 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))) @@ -78,18 +81,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/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index ffd715cbed8..4a5117ee252 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", diff --git a/packages/core/src/shared/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index 9f9fe9875b9..8a428b8e8b7 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) } } 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..be44ba89bd2 100644 --- a/packages/core/src/shared/utilities/processUtils.ts +++ b/packages/core/src/shared/utilities/processUtils.ts @@ -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,10 +80,6 @@ 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() @@ -82,6 +89,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,13 +122,17 @@ 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}%`) } } } @@ -248,6 +268,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/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/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/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/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/processUtils.test.ts b/packages/core/src/test/shared/utilities/processUtils.test.ts index 436ac48ecc4..65817d80a56 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 } } @@ -454,12 +456,12 @@ describe('ChildProcessTracker', function () { 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, + memory: defaultProcessWarnThresholds.memory + 1, } usageMock.returns(highCpu) @@ -480,7 +482,7 @@ describe('ChildProcessTracker', function () { tracker.add(runningProcess.childProcess) usageMock.returns({ - cpu: ChildProcessTracker.thresholds.cpu + 1, + cpu: defaultProcessWarnThresholds.cpu + 1, memory: 0, }) @@ -492,10 +494,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 +507,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/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/CHANGELOG.md b/packages/toolkit/CHANGELOG.md index ede66857244..8bceccdcf13 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,15 @@ +## 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..806a87d097b 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.59.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 From 8787af30a951748ef701452ec34e503effc066f5 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Tue, 6 May 2025 11:59:56 -0400 Subject: [PATCH 116/153] fix(amazonq): Bug fixes for LSP auth on agentic mode (#7231) ## Problem Agentic chat introduced code changes that break auth on LSP ## Solution Fixes for the auth flow: * Move instantiation of `AuthUtil` into `startLanguageServer` sinc ethe function uses several AuthUtil calls, which fail if it is not instantiated yet * Remove the dependency of LSP on glibc patch, since auth doesn't work if LSP is not started * Add URL opening capability to the new `ShowDocumentRequest` handler, and remove the old handler from `client.ts`. * Fix bug where session isn't restored upon start of extension when SSO is expired. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/extension.ts | 23 ++------ packages/amazonq/src/extensionNode.ts | 3 -- packages/amazonq/src/lsp/activation.ts | 6 +-- packages/amazonq/src/lsp/chat/messages.ts | 20 ------- .../amazonq/src/lsp/chat/webviewProvider.ts | 2 +- packages/amazonq/src/lsp/client.ts | 53 ++++++++++++------- .../core/src/codewhisperer/util/authUtil.ts | 3 ++ 7 files changed, 44 insertions(+), 66 deletions(-) diff --git a/packages/amazonq/src/extension.ts b/packages/amazonq/src/extension.ts index d489f694d34..222ebcaff87 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -3,13 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CredentialsStore, LoginManager, authUtils, initializeAuth } from 'aws-core-vscode/auth' -import { Auth, AuthUtils, CredentialsStore, LoginManager, initializeAuth } from 'aws-core-vscode/auth' -import { - activate as activateCodeWhisperer, - AuthUtil, - shutdown as shutdownCodeWhisperer, -} from 'aws-core-vscode/codewhisperer' +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' import { @@ -39,7 +34,6 @@ import { Experiments, isSageMaker, Commands, - isAmazonInternalOs, } from 'aws-core-vscode/shared' import { ExtStartUpSources } from 'aws-core-vscode/telemetry' import { VSCODE_EXTENSION_ID } from 'aws-core-vscode/utils' @@ -50,8 +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' -import { hasGlibcPatch } from './lsp/client' export const amazonQContextPrefix = 'amazonq' @@ -127,17 +119,12 @@ 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('amazonqLSP', true) || AuthUtil.instance.isInternalAmazonUser()) && - (!isAmazonInternalOs() || (await hasGlibcPatch())) - ) { - // start the Amazon Q LSP for internal users first - // for AL2, start LSP if glibc patch is found - await activateAmazonqLsp(context) - } if (!Experiments.instance.get('amazonqLSPInline', false)) { await activateInlineCompletion() } diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index 98be79c1920..2bcf5c152ce 100644 --- a/packages/amazonq/src/extensionNode.ts +++ b/packages/amazonq/src/extensionNode.ts @@ -19,7 +19,6 @@ 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 } from 'aws-core-vscode/telemetry' @@ -68,8 +67,6 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) { 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/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 81dd9e13515..b5a028aa0ae 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -36,9 +36,6 @@ import { ShowSaveFileDialogParams, LSPErrorCodes, tabBarActionRequestType, - ShowDocumentParams, - ShowDocumentResult, - ShowDocumentRequest, contextCommandsNotificationType, ContextCommandParams, openFileDiffNotificationType, @@ -423,23 +420,6 @@ 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, diff --git a/packages/amazonq/src/lsp/chat/webviewProvider.ts b/packages/amazonq/src/lsp/chat/webviewProvider.ts index 1a513f1df3f..3f9d273ab96 100644 --- a/packages/amazonq/src/lsp/chat/webviewProvider.ts +++ b/packages/amazonq/src/lsp/chat/webviewProvider.ts @@ -137,7 +137,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider { let qChat = undefined const init = () => { const vscodeApi = acquireVsCodeApi() - const hybridChatConnector = new HybridChatAdapter(${(await AuthUtil.instance.getChatAuthState()).amazonQ === 'connected'},${featureConfigData},${welcomeCount},${disclaimerAcknowledged},${regionProfileString},${disabledCommands},${isSMUS},${isSM},vscodeApi.postMessage) + const hybridChatConnector = new HybridChatAdapter(${AuthUtil.instance.isConnected()},${featureConfigData},${welcomeCount},${disclaimerAcknowledged},${regionProfileString},${disabledCommands},${isSMUS},${isSM},vscodeApi.postMessage) const commands = [hybridChatConnector.initialQuickActions[0]] qChat = amazonQChat.createChat(vscodeApi, {disclaimerAcknowledged: ${disclaimerAcknowledged}, pairProgrammingAcknowledged: ${pairProgrammingAcknowledged}, agenticMode: true, quickActionCommands: commands}, hybridChatConnector, ${JSON.stringify(featureConfigData)}); } diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 5f6f548b951..899c221d28e 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -23,12 +23,14 @@ 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 { @@ -37,7 +39,6 @@ import { globals, Experiments, Commands, - openUrl, validateNodeExe, getLogger, undefinedIfEmpty, @@ -45,9 +46,10 @@ import { isAmazonInternalOs, fs, oidcClientName, + openUrl, } from 'aws-core-vscode/shared' import { processUtils } from 'aws-core-vscode/shared' -import { activate } from './chat/activation' +import { activate as activateChat } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' import { auth2 } from 'aws-core-vscode/auth' import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config' @@ -144,9 +146,6 @@ export async function startLanguageServer( notifications: true, showSaveFileDialog: true, }, - q: { - developerProfiles: true, - }, }, logLevel: toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel), }, @@ -173,6 +172,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, () => { @@ -183,15 +183,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) => { @@ -201,6 +192,30 @@ 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, { @@ -266,9 +281,7 @@ export async function startLanguageServer( ) } - if (Experiments.instance.get('amazonqChatLSP', false)) { - activate(client, encryptionKey, resourcePaths.ui) - } + await activateChat(client, encryptionKey, resourcePaths.ui) toDispose.push( AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(sendProfileToLsp), diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index 5ff9a1c9a55..092b5f88d7c 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -88,6 +88,9 @@ export class AuthUtil implements IAuthProvider { async restore() { await this.session.restore() + if (!this.isConnected()) { + await this.refreshState() + } } async login(startUrl: string, region: string) { From 25643eee26a3ecd39662fe1f2aea98ec1ee49d9f Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Tue, 6 May 2025 18:36:14 +0000 Subject: [PATCH 117/153] Update version to snapshot version: 3.60.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/toolkit/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd4508b1b60..50e66f18b6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.59.0", + "version": "3.60.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 40e1790327e..cffb69d0c1a 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.59.0", + "version": "3.60.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 9f44fa6bb8dfad737df4f33240a0131e0275f4d1 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Tue, 6 May 2025 18:40:07 +0000 Subject: [PATCH 118/153] Release 3.60.0 --- package-lock.json | 4 ++-- packages/toolkit/.changes/3.60.0.json | 5 +++++ packages/toolkit/CHANGELOG.md | 4 ++++ packages/toolkit/package.json | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 packages/toolkit/.changes/3.60.0.json diff --git a/package-lock.json b/package-lock.json index 50e66f18b6c..c91bf2b987f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.60.0-SNAPSHOT", + "version": "3.60.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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 215c7c68cba..e21988fcd50 100644 --- a/packages/toolkit/CHANGELOG.md +++ b/packages/toolkit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.60.0 2025-05-06 + +- Miscellaneous non-user-facing changes + ## 3.59.0 2025-05-05 - Miscellaneous non-user-facing changes diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index cffb69d0c1a..107245f54f2 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.60.0-SNAPSHOT", + "version": "3.60.0", "extensionKind": [ "workspace" ], From 1836fee2f0f4cb3707368780f32cf50e78a5329b Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Tue, 6 May 2025 18:51:02 +0000 Subject: [PATCH 119/153] Update version to snapshot version: 1.66.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/amazonq/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 743c3c1e016..dbc13b85c21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.65.0", + "version": "1.66.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index f44f9fcc857..0553b16a973 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.65.0", + "version": "1.66.0-SNAPSHOT", "extensionKind": [ "workspace" ], From fdaaae1a5ba36686d400ca612c15d5a6cb0fdf89 Mon Sep 17 00:00:00 2001 From: Lei Gao <97199248+leigaol@users.noreply.github.com> Date: Tue, 6 May 2025 12:09:23 -0700 Subject: [PATCH 120/153] telemetry(amazonq): Add changed IDE diagnostics after user acceptance (#7130) ## Problem When user accepts suggestion from inline completion or chat, there can be a change in the current open editor's IDE diagnostics, this can be used as a measure for code suggestion quality. Ref: https://github.com/aws/aws-toolkit-jetbrains/pull/5613 This change is part of the server side workspace context. To reduce the risk of large blast radius, this change is only applied for users who are in the experiment of server side project context (both treatment and control), which is the Amzn idc users. E2E verifying request id: `97a3c4d4-0c78-4329-93ae-379d2ec66646`. ## Solution 1. Add changed IDE diagnostics after user acceptance --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../util/diagnosticsUtil.test.ts | 92 ++++++++++++++ .../commons/controllers/contentController.ts | 2 + packages/core/src/codewhisperer/index.ts | 1 + .../util/codeWhispererSession.ts | 5 +- .../src/codewhisperer/util/diagnosticsUtil.ts | 117 ++++++++++++++++++ .../src/codewhisperer/util/telemetryHelper.ts | 89 +++++++------ .../controllers/chat/telemetryHelper.ts | 80 +++++++++--- .../amazonq/common/contentController.test.ts | 6 +- 8 files changed, 332 insertions(+), 60 deletions(-) create mode 100644 packages/amazonq/test/unit/codewhisperer/util/diagnosticsUtil.test.ts create mode 100644 packages/core/src/codewhisperer/util/diagnosticsUtil.ts 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/core/src/amazonq/commons/controllers/contentController.ts b/packages/core/src/amazonq/commons/controllers/contentController.ts index 70744417451..97c828e6f9e 100644 --- a/packages/core/src/amazonq/commons/controllers/contentController.ts +++ b/packages/core/src/amazonq/commons/controllers/contentController.ts @@ -19,6 +19,7 @@ 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' @@ -49,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) diff --git a/packages/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 98b7f9239b1..4235ae28668 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -89,6 +89,7 @@ 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' 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/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/telemetryHelper.ts b/packages/core/src/codewhisperer/util/telemetryHelper.ts index 4bb3b92dc33..060a5ecb282 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/controllers/chat/telemetryHelper.ts b/packages/core/src/codewhispererChat/controllers/chat/telemetryHelper.ts index f2c447500da..2d9e01db9a0 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/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) From fc4f4edf530cecd87812c7505eb8b12f9552d217 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Tue, 6 May 2025 16:25:56 -0400 Subject: [PATCH 121/153] build(core): remove flare as codeowners (#7238) ## Problem Flare is marked as codeowners of the following: ``` packages/core/src/codewhispererChat/ @aws/flare packages/core/src/amazonq/ @aws/flare ``` AFAIK, the flare team hasn't done significant work in these folders, and does not have bandwidth to review changes here. - `packages/core/src/codewhispererChat/ ` will be mostly deleted when we complete the migration to Flare (ex. [here](https://github.com/aws/aws-toolkit-vscode/pull/7237)). - `packages/core/src/amazonq/ ` contains most changes to the amazon q extension, which seems out of scope for the Flare team. Having flare as codeowners requires the toolkits team to bypass their approval on any PRs touching files in these directories. This can lead to delayed merges and confusion. ## Solution - remove flare as a code owner. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .github/CODEOWNERS | 2 -- 1 file changed, 2 deletions(-) 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 From 018956474c58c1c867e53e74fd3af80826b9991f Mon Sep 17 00:00:00 2001 From: zuoyaofu Date: Tue, 6 May 2025 13:33:36 -0700 Subject: [PATCH 122/153] test(amazonq): full E2E test for /review (#7047) ## Problem Create E2E test for /review ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../amazonq/test/e2e/amazonq/review.test.ts | 415 ++++++++++++++++-- .../codewhisperer/commands/basicCommands.ts | 6 +- .../workspaceFolder/QCAFolder/RLinker.java | 248 +++++++++++ 3 files changed, 621 insertions(+), 48 deletions(-) create mode 100644 packages/core/src/testFixtures/workspaceFolder/QCAFolder/RLinker.java 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/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index c2978b56568..7fe6078a1d7 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -369,6 +369,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, @@ -387,9 +390,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/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 From 0acb7ae00aca6768de6bb77390fc280899cef5ff Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 6 May 2025 17:02:10 -0400 Subject: [PATCH 123/153] fix(logging): misleading "settings failed" message #7240 ## Problem Misleading log message when a setting returns `null`, even if the caller specified `defaultValue`: [error] Settings (amazonQ): failed to read "workspaceIndexCacheDirPath": Unexpected type cast: got object, expected primitive String `cast()` considers `null` to be "object" type, which is misleading. If the caller passed a `defaultValue`, they have intentionally opted-in to ignoring this situation, and *don't* want this kind of log message. ## Solution If a setting returns `null|undefined`, skip `cast()` and return `defaultValue` immediately. Don't log an error message. --- packages/core/src/shared/settings.ts | 4 ++++ 1 file changed, 4 insertions(+) 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]) } From e9ea8082ffe0b9968a873437407d0b6b31b9e1a5 Mon Sep 17 00:00:00 2001 From: chengoramazon <143654391+chengoramazon@users.noreply.github.com> Date: Tue, 6 May 2025 17:19:07 -0400 Subject: [PATCH 124/153] test(featureDev): use env var to enable very slow test #7201 ## Problem The default 5-minute timeout was insufficient for Amazon Q Feature Dev tests that involve code generation with multiple iterations. This was causing tests to fail prematurely before they could complete their full execution cycle. ## Solution Extended the test timeout to 15 minutes (900,000ms) specifically for the code generation test with multi-iteration scenarios. This provides adequate time for multi-iteration code generation tests to complete their execution while maintaining the existing test behavior. --- CONTRIBUTING.md | 1 + packages/amazonq/test/e2e/amazonq/featureDev.test.ts | 6 ++++++ 2 files changed, 7 insertions(+) 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/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() From 98b0d5d1e9b1ea594e6ce0a76045072528b5fc7a Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Tue, 6 May 2025 21:02:12 -0700 Subject: [PATCH 125/153] refactor(amazonq): reduce extra call of listAvailableCustomization (#7242) ## Problem Customization will only have "1" parent profile, so theoretically we don't need another call of listAvailableCustomization to check if the new profile has access to given customization or not. Instead, we could simply switch to default customization (foundation). ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../src/codewhisperer/util/customizationUtil.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index 600317c53e0..d989fadf05c 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -59,7 +59,7 @@ export const onProfileChangedListener: (event: ProfileChangedEvent) => any = asy if (event.intent === 'customization') { return } - const logger = getLogger() + if (!event.profile) { await setSelectedCustomization(baseCustomization) return @@ -69,16 +69,7 @@ export const onProfileChangedListener: (event: ProfileChangedEvent) => any = asy 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() - } + await switchToBaseCustomizationAndNotify() } } From bb11bd9a4b498b2129085f43217b6b712dbf8c44 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Wed, 7 May 2025 12:14:22 -0400 Subject: [PATCH 126/153] feat(telemetry): metrics with missing fields are warned in the logs. (#7196) ## Problem A metric was forwarded from Flare with missing fields. This resulted in some confusion about why the metric wasn't showing up properly in Kibana. ## Solution - refactor validation to check for missingFields and log a warning. - Ideally, we would throw in CI for these, but there is currently a significant number of metrics emitted without required fields. - add tests for this validation. ## Future Work - Fix existing cases of emitting with missing required fields, so we can throw in CI when this happens. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/core/src/shared/logger/logger.ts | 1 + packages/core/src/shared/telemetry/util.ts | 58 +++++---- .../src/shared/utilities/collectionUtils.ts | 10 +- .../src/shared/utilities/functionUtils.ts | 41 +++++++ .../core/src/shared/utilities/processUtils.ts | 13 +-- .../src/test/shared/telemetry/util.test.ts | 110 ++++++++++++++++++ .../shared/utilities/functionUtils.test.ts | 78 ++++++++++++- .../shared/utilities/processUtils.test.ts | 23 ++-- 8 files changed, 290 insertions(+), 44 deletions(-) diff --git a/packages/core/src/shared/logger/logger.ts b/packages/core/src/shared/logger/logger.ts index 38ff40627c1..b398ff93162 100644 --- a/packages/core/src/shared/logger/logger.ts +++ b/packages/core/src/shared/logger/logger.ts @@ -20,6 +20,7 @@ export type LogTopic = | 'unknown' | 'nextEditPrediction' | 'resourceCache' + | 'telemetry' class ErrorLog { constructor( 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/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index 8a428b8e8b7..a5a5e83e7d6 100644 --- a/packages/core/src/shared/utilities/collectionUtils.ts +++ b/packages/core/src/shared/utilities/collectionUtils.ts @@ -580,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/processUtils.ts b/packages/core/src/shared/utilities/processUtils.ts index be44ba89bd2..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. */ @@ -81,7 +81,6 @@ export interface ProcessStats { export class ChildProcessTracker { static readonly pollingInterval: number = 10000 // Check usage every 10 seconds static readonly logger = logger.getLogger('childProcess') - static readonly loggedPids = new CircularBuffer(1000) #processByPid: Map = new Map() #pids: PollingSet @@ -137,12 +136,12 @@ export class ChildProcessTracker { } } - 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() 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/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 65817d80a56..fda6d93293e 100644 --- a/packages/core/src/test/shared/utilities/processUtils.test.ts +++ b/packages/core/src/test/shared/utilities/processUtils.test.ts @@ -395,10 +395,6 @@ describe('ChildProcessTracker', function () { usageMock = sinon.stub(ChildProcessTracker.prototype, 'getUsage') }) - beforeEach(function () { - ChildProcessTracker.loggedPids.clear() - }) - afterEach(function () { tracker.clear() usageMock.reset() @@ -451,7 +447,7 @@ 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) @@ -459,18 +455,25 @@ describe('ChildProcessTracker', function () { cpu: defaultProcessWarnThresholds.cpu + 1, memory: 0, } - const highMemory: ProcessStats = { - cpu: 0, - memory: defaultProcessWarnThresholds.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') From 4b0ec4ddc73fa565cb0be4060b1dd3473def623b Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Wed, 7 May 2025 13:39:47 -0400 Subject: [PATCH 127/153] fix(amazonq): Fix showSsoPrompt unit tests (#7236) ## Problem `showConnectionPrompt` unit tests were failing ## Solution * Ensure DeviceFlow is used for auth * Stub login * Assert whether the login was called with builder ID instead of if the log line was emitted --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../unit/codewhisperer/util/showSsoPrompt.test.ts | 15 +++++++++++---- packages/core/src/auth/index.ts | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) 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/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' From 1d9064fcfa50ea3d7306311d3277953828ec14ef Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Wed, 7 May 2025 16:19:13 -0700 Subject: [PATCH 128/153] fix(amazonq): Minor string change in quickActionCommands for /test (#7246) ## Problem - IDE shows `Generate unit tests (python & java) for selected code` in quick actions but Q supports all the languages. ## Solution - Made change accordingly. ![image](https://github.com/user-attachments/assets/fc0a0967-ebdd-471b-b7d6-8889c81da075) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/core/src/amazonq/webview/ui/quickActions/generator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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', }, ] : []), From 710f25c4d9b905c21fc32c610e5a91ceb7b3b6a0 Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Wed, 7 May 2025 21:37:15 -0400 Subject: [PATCH 129/153] fix(amazonq): add a more descriptive issue for firewall problems (#7251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem If a users firewall is blocking downloading the zips or reaching the manifest its not clear to the user what to do ## Solution If the user: 1. doesn't have the latest version cached 2. download of the manifest failed 3. they don't have any previous language servers downloaded it might be a firewall issue. Rather than just `Unable to find a compatible version of the Language Server` we show a more descriptive message to the user Screenshot 2025-05-07 at 2 48 39 PM --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../amazonq/test/e2e/lsp/lspInstallerUtil.ts | 35 ++++++++++++--- .../core/src/shared/lsp/baseLspInstaller.ts | 1 + packages/core/src/shared/lsp/lspResolver.ts | 45 +++++++++++++++---- .../core/src/shared/lsp/manifestResolver.ts | 1 - 4 files changed, 66 insertions(+), 16 deletions(-) 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/core/src/shared/lsp/baseLspInstaller.ts b/packages/core/src/shared/lsp/baseLspInstaller.ts index 27e19df6b3a..0aeca1dfda4 100644 --- a/packages/core/src/shared/lsp/baseLspInstaller.ts +++ b/packages/core/src/shared/lsp/baseLspInstaller.ts @@ -45,6 +45,7 @@ export abstract class BaseLspInstaller { - 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', @@ -142,6 +158,7 @@ export class LanguageServerResolver { assetDirectory: cacheDirectory, } } else { + await this.cleanupVersion(latestVersion.serverVersion) throw new ToolkitError('Failed to download server from remote', { code: 'RemoteDownloadFailed' }) } } finally { @@ -149,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, @@ -178,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 @@ -203,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 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... From 0b169a846ba3215a695633ee8ac9245567fed2fe Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Thu, 8 May 2025 10:12:30 -0400 Subject: [PATCH 130/153] telemetry(amazonq): add an error code for when node validation fails. (#7254) ## Problem These errors show up in telemetry with reason `Error`. All of the `reasonDesc` are different since they include unique PID values. This makes it hard to count/quantify how often this is happening. ## Solution - attach a unique error code to these errors. For when node can't be run we have `FailedToRunNode`, and when the language server can't be started we have `FailedToStartLanguageServer`. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/core/src/shared/lsp/utils/platform.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 8b775433277..2555793ceb5 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -40,7 +40,7 @@ export async function validateNodeExe(nodePath: string[], lsp: string, args: str 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 …`. @@ -68,7 +68,8 @@ export async function validateNodeExe(nodePath: string[], lsp: string, args: str }) 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 { From ae7c2227e6ddc67dc883e0d0e79fb6e52435b1d4 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 8 May 2025 10:13:17 -0400 Subject: [PATCH 131/153] fix(amazonq): Add AuthUtil test suite and fix unit tests (#7249) ## Problem Unit tests for `amazonq_backed` and `regionProfileManager` are broken or disabled, because they are not working with the LSP auth setup ## Solution * Create a new test util to setup AuthUtil for tests, stubbing the LSP client methods * Rework the unit test where necessary to use the AuthUtil on Flare identity server * Move `amazonq_backed` to the `amazonq` package, since the AuthUtil won't instantiate correctly if the unit test runs under `toolkits` test suite --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../test/unit/amazonq/backend_amazonq.test.ts | 119 ++++++++++++++++ .../region/regionProfileManager.test.ts | 5 +- .../service/recommendationHandler.test.ts | 6 +- .../tracker/codewhispererTracker.test.ts | 4 +- packages/core/src/codewhisperer/index.ts | 1 + packages/core/src/login/webview/index.ts | 1 + .../webview/vue/amazonq/backend_amazonq.ts | 8 +- packages/core/src/test/index.ts | 1 + .../login/webview/vue/backend_amazonq.test.ts | 130 ------------------ packages/core/src/test/testAuthUtil.ts | 43 ++++++ 10 files changed, 175 insertions(+), 143 deletions(-) create mode 100644 packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts delete mode 100644 packages/core/src/test/login/webview/vue/backend_amazonq.test.ts create mode 100644 packages/core/src/test/testAuthUtil.ts 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..205c19ad798 --- /dev/null +++ b/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts @@ -0,0 +1,119 @@ +/*! + * 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 + + await createTestAuthUtil() + + beforeEach(function () { + 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 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/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index 9a4bed381d1..b60d9985eb3 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 = { @@ -22,6 +23,8 @@ describe('RegionProfileManager', function () { description: 'foo description', } + await createTestAuthUtil() + async function setupConnection(type: 'builderId' | 'idc') { if (type === 'builderId') { await AuthUtil.instance.login(constants.builderIdStartUrl, region) 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/core/src/codewhisperer/index.ts b/packages/core/src/codewhisperer/index.ts index 930b168beec..73291308cfa 100644 --- a/packages/core/src/codewhisperer/index.ts +++ b/packages/core/src/codewhisperer/index.ts @@ -93,6 +93,7 @@ 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' 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 01a6c1279d7..fe12b214a15 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' @@ -114,9 +114,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 +176,7 @@ export class AmazonQLoginWebview extends CommonAuthWebview { this.storeMetricMetadata({ authEnabledFeatures: 'codewhisperer', isReAuth: true, - ...(await getTelemetryMetadataForConn()), + ...(await AuthUtil.instance.getTelemetryMetadata()), result: 'Cancelled', }) 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/testAuthUtil.ts b/packages/core/src/test/testAuthUtil.ts new file mode 100644 index 00000000000..14e7045739a --- /dev/null +++ b/packages/core/src/test/testAuthUtil.ts @@ -0,0 +1,43 @@ +/*! + * 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, + } + + AuthUtil.create(mockLspAuth as LanguageClientAuth) +} From 9b873dca991417fdf95586a437789b04e06afcf0 Mon Sep 17 00:00:00 2001 From: Hweinstock <42325418+Hweinstock@users.noreply.github.com> Date: Thu, 8 May 2025 11:37:11 -0400 Subject: [PATCH 132/153] test(amazonq): reduce flakiness by avoiding unnecessary fs operations. (#7259) ## Problem This test has been very flaky, and constantly failing CI (https://github.com/aws/aws-toolkit-vscode/issues/7187). This test is run for almost 40 different cases, and does the following: - create a text document - write it to the filesystem. - run the check on the text document. One of the core issues is that when we write the text document to the filesystem, we create a new test workspace folder for each case. This involves creating a directory with a random id for each of the almost 40 different cases. These excessive file system operations could be leading to the flakiness in CI. ## Solution - reuse the same test workspace folder across all test cases. This should cut the file systems operations in half. ## Future Work - If this doesn't reduce flakiness, we could avoid writing the text document to the fs since its not needed by the underlying test, but this involves mocking the text document which is undesirable imo. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../codewhisperer/util/runtimeLanguageContext.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts b/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts index 9cf61920861..59c3771abb4 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) }) From faeeb98e511be4df52f19199c4128ff80c71350d Mon Sep 17 00:00:00 2001 From: Jiatong Li Date: Thu, 8 May 2025 08:41:36 -0700 Subject: [PATCH 133/153] feat(amazonq): pass workspaceIdentifier when initializing AmazonQ lsp #7252 ## Problem AmazonQ LSP needs an identifier for the IDE workspace, which should be stable and unique for each workspace, regardless of IDE restarts or system reboots. ## Solution Use [ExtensionContext.storageUri](https://code.visualstudio.com/api/references/vscode-api#ExtensionContext.storageUri) as such an identifier and pass it when initializing AmazonQ lsp. --- packages/amazonq/src/lsp/client.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index a8cb8d76a40..5cbf174e577 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -131,6 +131,9 @@ export async function startLanguageServer( showSaveFileDialog: true, }, }, + contextConfiguration: { + workspaceIdentifier: extensionContext.storageUri, + }, logLevel: toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel), }, credentials: { From 734bd989cec31e715f89bcd2f544397dada798c4 Mon Sep 17 00:00:00 2001 From: Tom Zu <138054255+tomcat323@users.noreply.github.com> Date: Thu, 8 May 2025 13:40:07 -0400 Subject: [PATCH 134/153] fix(nep): truncate editor state (#7256) ## Problem We introduced `editorState` in data instrumentation launch. The service has a requirement of 40k character limit for the `text` field. ## Solution Implement a check on text length. If the text length exceeds 40k characters, section 20k max characters from the left and right side of current cursor position, so the final text is always less than 40k. validated prod endpoint inline working for files > 40k characters. Example request id: `57bbbe65-fbe7-47fc-81c4-c65262f47ce8` --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- ...-75375702-36b5-4e89-af57-4afe983a7238.json | 4 ++++ .../src/codewhisperer/models/constants.ts | 3 +++ .../src/codewhisperer/util/editorContext.ts | 21 +++++++++++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json diff --git a/packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json b/packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json new file mode 100644 index 00000000000..83796afaa55 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Avoid inline completion 'Improperly formed request' errors when file is too large" +} diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index dc0426376ce..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 */ diff --git a/packages/core/src/codewhisperer/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts index 0861b982d13..58301e176f6 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' @@ -216,13 +217,29 @@ export function getTabSize(): number { 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: editor.document.getText(), + text: fileText, }, cursorState: { position: { From 772ea7267fe07b6755f7161252e6c3cbe2ceea7d Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 8 May 2025 16:55:51 -0400 Subject: [PATCH 135/153] test(amazonq): add unit tests for authUtil (#7263) ## Problem There are no unit tests for the new AuthUtil on Flare LSP ## Solution * Add unit test for core AuthUtil functionality * Create a test util for all tests where AuthUtil is needed * Ensure all tests that interact with the AuthUtil consume the new test util * Add destroy method to the AuthUtil class to simplify cleanup for unit tests --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../test/unit/amazonq/backend_amazonq.test.ts | 7 +- .../commands/invokeRecommendation.test.ts | 3 +- .../region/regionProfileManager.test.ts | 5 +- .../service/inlineCompletionService.test.ts | 8 +- .../service/keyStrokeHandler.test.ts | 4 + .../unit/codewhisperer/util/authUtil.test.ts | 608 ++++++------------ .../core/src/codewhisperer/util/authUtil.ts | 5 + .../test/amazonq/customizationUtil.test.ts | 8 +- .../codewhisperer/startSecurityScan.test.ts | 12 +- .../codewhispererChat/editor/codelens.test.ts | 8 +- .../src/test/shared/featureConfig.test.ts | 11 +- packages/core/src/test/testAuthUtil.ts | 3 + 12 files changed, 231 insertions(+), 451 deletions(-) diff --git a/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts b/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts index 205c19ad798..5d9972019f4 100644 --- a/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts +++ b/packages/amazonq/test/unit/amazonq/backend_amazonq.test.ts @@ -16,9 +16,8 @@ describe('Amazon Q Login', async function () { let sandbox: sinon.SinonSandbox let backend: backendAmazonQ.AmazonQLoginWebview - await createTestAuthUtil() - - beforeEach(function () { + beforeEach(async function () { + await createTestAuthUtil() sandbox = sinon.createSandbox() backend = new backendAmazonQ.AmazonQLoginWebview() }) @@ -101,6 +100,8 @@ describe('Amazon Q Login', async function () { }) it('signs out of reauth and emits telemetry', async function () { + await getStartUrl.connectToEnterpriseSso(startUrl, region) + await backend.signout() assert.ok(!AuthUtil.instance.isConnected()) 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 b60d9985eb3..ba4001e5a68 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -23,8 +23,6 @@ describe('RegionProfileManager', async function () { description: 'foo description', } - await createTestAuthUtil() - async function setupConnection(type: 'builderId' | 'idc') { if (type === 'builderId') { await AuthUtil.instance.login(constants.builderIdStartUrl, region) @@ -37,7 +35,8 @@ describe('RegionProfileManager', async function () { } } - beforeEach(function () { + beforeEach(async function () { + await createTestAuthUtil() regionProfileManager = new RegionProfileManager(AuthUtil.instance) }) 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/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/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index 092b5f88d7c..362b1ae7157 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -82,6 +82,11 @@ 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 } diff --git a/packages/core/src/test/amazonq/customizationUtil.test.ts b/packages/core/src/test/amazonq/customizationUtil.test.ts index bb852afba2e..7e9e209b31f 100644 --- a/packages/core/src/test/amazonq/customizationUtil.test.ts +++ b/packages/core/src/test/amazonq/customizationUtil.test.ts @@ -19,7 +19,7 @@ import { 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' @@ -30,13 +30,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/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/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/testAuthUtil.ts b/packages/core/src/test/testAuthUtil.ts index 14e7045739a..4feefec2d68 100644 --- a/packages/core/src/test/testAuthUtil.ts +++ b/packages/core/src/test/testAuthUtil.ts @@ -39,5 +39,8 @@ export async function createTestAuthUtil() { 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) } From 529f833454dc29fddd68511f84266f9015215f4c Mon Sep 17 00:00:00 2001 From: Will Lo <96078566+Will-ShaoHua@users.noreply.github.com> Date: Thu, 8 May 2025 13:58:02 -0700 Subject: [PATCH 136/153] fix(amazonq): Revert refactor(amazonq): reduce extra call of listAvailableCustomization (#7242) (#7266) This reverts commit 98b0d5d1e9b1ea594e6ce0a76045072528b5fc7a. ## Problem It regress #7181 and make 7181 not working: profile will be changed, but customization will be swapped to default always. ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../src/codewhisperer/util/customizationUtil.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index d989fadf05c..600317c53e0 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -59,7 +59,7 @@ export const onProfileChangedListener: (event: ProfileChangedEvent) => any = asy if (event.intent === 'customization') { return } - + const logger = getLogger() if (!event.profile) { await setSelectedCustomization(baseCustomization) return @@ -69,7 +69,16 @@ export const onProfileChangedListener: (event: ProfileChangedEvent) => any = asy const selectedCustomization = getSelectedCustomization() // No need to validate base customization which has empty arn. if (selectedCustomization.arn.length > 0) { - await switchToBaseCustomizationAndNotify() + 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() + } } } From b49a69f6c230ad580fd64bc6037a7d350c2c1f69 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 8 May 2025 16:22:13 -0400 Subject: [PATCH 137/153] fix(agentic chat): Temporarily disable E2E tests --- packages/amazonq/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 0553b16a973..0beb045bd73 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -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", From 8a02d60b5c0ba292bcf2c8026b2417a034086be5 Mon Sep 17 00:00:00 2001 From: Tai Lai Date: Thu, 8 May 2025 14:09:21 -0700 Subject: [PATCH 138/153] fix(amazonq): agent tabs open with prompt options (#7265) ## Problem Inconsistent behavior when opening agent tabs (/review, /doc, etc). When the tab is reused it keeps the prompt input options visible, but when a new tab is created it doesn't. https://github.com/user-attachments/assets/2ff7264f-f7a3-46f6-9a34-e29835768833 ## Solution Set `promptInputOptions` to empty when an existing tab is reused. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- .../Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json | 4 ++++ packages/core/src/amazonq/webview/ui/quickActions/handler.ts | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json diff --git a/packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json b/packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json new file mode 100644 index 00000000000..02c9a67d136 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Named agent tabs sometimes open with unnecessary input options" +} diff --git a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts index fe124d1fc0c..f0d707247e9 100644 --- a/packages/core/src/amazonq/webview/ui/quickActions/handler.ts +++ b/packages/core/src/amazonq/webview/ui/quickActions/handler.ts @@ -355,6 +355,8 @@ export class QuickActionHandler { loadingChat: true, cancelButtonWhenLoading: false, }) + } else { + this.mynahUI.updateStore(affectedTabId, { promptInputOptions: [] }) } if (affectedTabId && this.isHybridChatEnabled) { From 7843a9fbba8f6a390c8f9be539b5333fb255b759 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 8 May 2025 17:37:11 -0400 Subject: [PATCH 139/153] Fix linter --- .../codewhisperer/region/regionProfileManager.test.ts | 4 ++-- .../src/codewhisperer/region/regionProfileManager.ts | 4 ---- .../core/src/test/amazonq/customizationUtil.test.ts | 11 +++-------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index e34ecf6c6c2..505614b7fc5 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -257,7 +257,7 @@ describe('RegionProfileManager', async function () { await assert.rejects( async () => { - await sut.createQClient({ + await regionProfileManager.createQClient({ name: 'foo', region: 'ap-east-1', arn: 'arn', @@ -269,7 +269,7 @@ describe('RegionProfileManager', async function () { await assert.rejects( async () => { - await sut.createQClient({ + await regionProfileManager.createQClient({ name: 'foo', region: 'unknown-somewhere', arn: 'arn', diff --git a/packages/core/src/codewhisperer/region/regionProfileManager.ts b/packages/core/src/codewhisperer/region/regionProfileManager.ts index b9dfee0a512..e965052064d 100644 --- a/packages/core/src/codewhisperer/region/regionProfileManager.ts +++ b/packages/core/src/codewhisperer/region/regionProfileManager.ts @@ -126,10 +126,6 @@ export class RegionProfileManager { return this.cache.getResource() } - async getProfiles(): Promise { - return this.cache.getResource() - } - async listRegionProfile(): Promise { this._profiles = [] diff --git a/packages/core/src/test/amazonq/customizationUtil.test.ts b/packages/core/src/test/amazonq/customizationUtil.test.ts index 394627f5a50..19e59b91c03 100644 --- a/packages/core/src/test/amazonq/customizationUtil.test.ts +++ b/packages/core/src/test/amazonq/customizationUtil.test.ts @@ -26,17 +26,12 @@ import { createTestAuthUtil } from '../testAuthUtil' const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' describe('customizationProvider', function () { - let auth: ReturnType - let ssoConn: SsoConnection let regionProfileManager: RegionProfileManager beforeEach(async () => { - auth = createTestAuth(globals.globalState) - ssoConn = await auth.createInvalidSsoConnection( - createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: amazonQScopes }) - ) - - regionProfileManager = new RegionProfileManager(() => ssoConn) + createTestAuth(globals.globalState) + await createTestAuthUtil() + regionProfileManager = new RegionProfileManager(AuthUtil.instance) }) afterEach(() => { From 153f8b3d1efea1ba1a4d0da8539295d3d7f45ee3 Mon Sep 17 00:00:00 2001 From: opieter-aws Date: Thu, 8 May 2025 18:00:43 -0400 Subject: [PATCH 140/153] Fix unit test --- .../codewhisperer/region/regionProfileManager.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts index 505614b7fc5..aa79e9052bd 100644 --- a/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/region/regionProfileManager.test.ts @@ -61,12 +61,12 @@ describe('RegionProfileManager', async 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.listRegionProfile() + 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', From 4369fb507fa79f2f6a56b8c5f3dd5b34f272733e Mon Sep 17 00:00:00 2001 From: Josh Pinkney <103940141+jpinkney-aws@users.noreply.github.com> Date: Thu, 8 May 2025 21:09:13 -0400 Subject: [PATCH 141/153] fix(amazonq): flare clientId changes on every instance (#7273) ## Problem clientId from `clientParams.initializationOptions?.aws?.clientInfo?.clientId` is random on every restart ## Solution use the client id from telemetry utils --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 5cbf174e577..e3b58455d77 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -5,7 +5,6 @@ import vscode, { env, version } from 'vscode' import * as nls from 'vscode-nls' -import * as crypto from 'crypto' import { LanguageClient, LanguageClientOptions, RequestType, State } from 'vscode-languageclient' import { InlineCompletionManager } from '../app/inline/completion' import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth' @@ -34,6 +33,7 @@ import { getOptOutPreference, isAmazonInternalOs, fs, + getClientId, } from 'aws-core-vscode/shared' import { processUtils } from 'aws-core-vscode/shared' import { activate } from './chat/activation' @@ -120,7 +120,7 @@ export async function startLanguageServer( name: 'AmazonQ-For-VSCode', version: '0.0.1', }, - clientId: crypto.randomUUID(), + clientId: getClientId(globals.globalState), }, awsClientCapabilities: { q: { From 0d97988720489dfecd59991c449988ab760b6824 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 9 May 2025 01:16:29 +0000 Subject: [PATCH 142/153] Release 1.66.0 --- package-lock.json | 4 ++-- packages/amazonq/.changes/1.66.0.json | 14 ++++++++++++++ ...g Fix-75375702-36b5-4e89-af57-4afe983a7238.json | 4 ---- ...g Fix-b873a959-2742-4440-badc-c90c6ac754c3.json | 4 ---- packages/amazonq/CHANGELOG.md | 5 +++++ packages/amazonq/package.json | 2 +- 6 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 packages/amazonq/.changes/1.66.0.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json delete mode 100644 packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json diff --git a/package-lock.json b/package-lock.json index c5a890c2260..5c66ecf29e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.1", + "ts-node": "^10.9.2", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.66.0-SNAPSHOT", + "version": "1.66.0", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" 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-75375702-36b5-4e89-af57-4afe983a7238.json b/packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json deleted file mode 100644 index 83796afaa55..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-75375702-36b5-4e89-af57-4afe983a7238.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Avoid inline completion 'Improperly formed request' errors when file is too large" -} diff --git a/packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json b/packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json deleted file mode 100644 index 02c9a67d136..00000000000 --- a/packages/amazonq/.changes/next-release/Bug Fix-b873a959-2742-4440-badc-c90c6ac754c3.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Bug Fix", - "description": "Named agent tabs sometimes open with unnecessary input options" -} diff --git a/packages/amazonq/CHANGELOG.md b/packages/amazonq/CHANGELOG.md index b5ceba33c7c..197aecdfdf6 100644 --- a/packages/amazonq/CHANGELOG.md +++ b/packages/amazonq/CHANGELOG.md @@ -1,3 +1,8 @@ +## 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 diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 0553b16a973..4197556075d 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.66.0-SNAPSHOT", + "version": "1.66.0", "extensionKind": [ "workspace" ], From dcaeb535747bb694a4637d155823aa8e800aab72 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 9 May 2025 13:56:54 +0000 Subject: [PATCH 143/153] Update version to snapshot version: 3.61.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/toolkit/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c91bf2b987f..dcdfcc8b0d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -28098,7 +28098,7 @@ }, "packages/toolkit": { "name": "aws-toolkit-vscode", - "version": "3.60.0", + "version": "3.61.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index 107245f54f2..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.60.0", + "version": "3.61.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 91e5039d2d2127fedfbdafc9b212e1984c64326b Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Fri, 9 May 2025 13:58:47 +0000 Subject: [PATCH 144/153] Update version to snapshot version: 1.67.0-SNAPSHOT --- package-lock.json | 4 ++-- packages/amazonq/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c66ecf29e7..4c9375c6b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-sh": "^0.14.0", "pretty-quick": "^4.0.0", - "ts-node": "^10.9.2", + "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.95.0", "webpack-cli": "^5.1.4", @@ -26384,7 +26384,7 @@ }, "packages/amazonq": { "name": "amazon-q-vscode", - "version": "1.66.0", + "version": "1.67.0-SNAPSHOT", "license": "Apache-2.0", "dependencies": { "aws-core-vscode": "file:../core/" diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 4197556075d..f9d466f5767 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.66.0", + "version": "1.67.0-SNAPSHOT", "extensionKind": [ "workspace" ], From 1085a8de0d19e2989693fa4616cb265f9705ecc6 Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Fri, 9 May 2025 12:22:46 -0400 Subject: [PATCH 145/153] telemetry(amazonq): Emit metric on server crash (#7278) When the server crashes and then restarts again, we will emit a metric to indicate it crashed. When querying look for: `metadata.metricName: languageServer_crash` & `metadata.id: AmazonQ` --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/amazonq/src/lsp/client.ts | 7 +++++++ packages/core/src/shared/telemetry/vscodeTelemetry.json | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index e3b58455d77..4735d9cbc8c 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -39,6 +39,7 @@ import { processUtils } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config' +import { telemetry } from 'aws-core-vscode/telemetry' const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') @@ -288,6 +289,12 @@ function onServerRestartHandler(client: LanguageClient, auth: AmazonQLspAuth) { 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 auth.refreshConnection(true) }) diff --git a/packages/core/src/shared/telemetry/vscodeTelemetry.json b/packages/core/src/shared/telemetry/vscodeTelemetry.json index 4a5117ee252..b28aeec4847 100644 --- a/packages/core/src/shared/telemetry/vscodeTelemetry.json +++ b/packages/core/src/shared/telemetry/vscodeTelemetry.json @@ -1019,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", From a14b9a215b63144f63745299fd0f6c74bbe68150 Mon Sep 17 00:00:00 2001 From: Adam Khamis <110852798+akhamis-amzn@users.noreply.github.com> Date: Fri, 9 May 2025 15:52:44 -0400 Subject: [PATCH 146/153] telemetry(amazonq): expose FileCreationFailed exceptions #7260 ## Problem FileCreationFailed exceptions are displayed as UnknownException in telemetry. This exception is new and we want to separate this out from other unknown exceptions. ## Solution Return API service error with `FileCreationFailedException` --- .../core/src/amazonqFeatureDev/session/sessionState.ts | 8 ++++++++ 1 file changed, 8 insertions(+) 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'), From 05cde57b3d526d3b69727b3f7a40c1ca585fb6ba Mon Sep 17 00:00:00 2001 From: Na Yue Date: Fri, 9 May 2025 15:15:14 -0700 Subject: [PATCH 147/153] fix(lsp): send extension version to Q LSP #7279 ## Problem Extension version sent to Q LSP is hardcoded. ## Solution Ssend the actual extension version BEFORE: aws-sdk-nodejs/2.1692.0 darwin/v23.10.0 AWS-Language-Servers AWS-CodeWhisperer/0.1.0 AmazonQ-For-VSCode/0.0.1 Visual-Studio-Code---Insiders/1.100.0-insider ClientId/c342ab45-6aba-4118-b48c-44dcedb10a78 promise AFTER aws-sdk-nodejs/2.1692.0 darwin/v23.10.0 AWS-Language-Servers AWS-CodeWhisperer/0.1.0 AmazonQ-For-VSCode/testPluginVersion Visual-Studio-Code---Insiders/1.100.0-insider ClientId/c342ab45-6aba-4118-b48c-44dcedb10a78 promise --- packages/amazonq/src/lsp/client.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 4735d9cbc8c..d20f8067103 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -34,6 +34,7 @@ import { isAmazonInternalOs, fs, getClientId, + extensionVersion, } from 'aws-core-vscode/shared' import { processUtils } from 'aws-core-vscode/shared' import { activate } from './chat/activation' @@ -119,7 +120,7 @@ export async function startLanguageServer( version: version, extension: { name: 'AmazonQ-For-VSCode', - version: '0.0.1', + version: extensionVersion, }, clientId: getClientId(globals.globalState), }, From 4b091678205c84954735a2a1ff1f5c087c4f7116 Mon Sep 17 00:00:00 2001 From: Laxman Reddy <141967714+laileni-aws@users.noreply.github.com> Date: Fri, 9 May 2025 16:00:28 -0700 Subject: [PATCH 148/153] fix(amazonq): adding logs for the agentic chat telemetry events (#7276) ## Problem - No logs is being emitted for telemetry events. ## Solution - Adding logs if telemetry events are emitted. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/chat/messages.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 9578858b708..38a52f72f9c 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -94,6 +94,7 @@ export function registerLanguageServerEventListener(languageClient: LanguageClie 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) } }) From ffc0cb47cfd017a6ec033613a6d453f4cd433eb3 Mon Sep 17 00:00:00 2001 From: Jiatong Li Date: Mon, 12 May 2025 14:34:05 -0700 Subject: [PATCH 149/153] fix(amazonq): pass uri.path as workspaceIdentifier when initializing #7291 ## Problem `workspaceIdentifier` should be a string: - https://github.com/aws/language-server-runtimes/pull/497 ## Solution Pass `extensionContext.storageUri?.path`. --- packages/amazonq/src/lsp/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index d20f8067103..4af113b13c4 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -134,7 +134,7 @@ export async function startLanguageServer( }, }, contextConfiguration: { - workspaceIdentifier: extensionContext.storageUri, + workspaceIdentifier: extensionContext.storageUri?.path, }, logLevel: toAmazonQLSPLogLevel(globals.logOutputChannel.logLevel), }, From 2d898fbac0bb0418ffc51b22d38202617bff2bfa Mon Sep 17 00:00:00 2001 From: Avi Alpert <131792194+avi-alpert@users.noreply.github.com> Date: Mon, 12 May 2025 17:36:08 -0400 Subject: [PATCH 150/153] deps: bump @aws-toolkits/telemetry to 1.0.318 #7290 ## Problem New telemetry metrics were [added](https://github.com/aws/aws-toolkit-common/pull/1023) to aws-toolkit-common ## Solution Consume latest version of aws-toolkit-common package --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0a36cb6df6a..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.317", + "@aws-toolkits/telemetry": "^1.0.318", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", @@ -10760,11 +10760,10 @@ } }, "node_modules/@aws-toolkits/telemetry": { - "version": "1.0.317", - "resolved": "https://registry.npmjs.org/@aws-toolkits/telemetry/-/telemetry-1.0.317.tgz", - "integrity": "sha512-QFLBFfHZjuB2pBd1p0Tn/GMKTYYQu3/nrlj0Co7EkqozvDNDG0nTjxtkXxotbwjrqVD5Sv8i46gEdgsyQ7at3w==", + "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", diff --git a/package.json b/package.json index 30f0497cdb2..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.317", + "@aws-toolkits/telemetry": "^1.0.318", "@playwright/browser-chromium": "^1.43.1", "@stylistic/eslint-plugin": "^2.11.0", "@types/he": "^1.2.3", From 8e85476708e0d2e9467cc7afe13a42661de78f1d Mon Sep 17 00:00:00 2001 From: Brad Skaggs <126105424+brdskggs@users.noreply.github.com> Date: Tue, 13 May 2025 11:54:16 -0400 Subject: [PATCH 151/153] fix(amazonq): use neighbor cells as completion context in Notebook #7086 ## Problem VS Code treats each cell in a notebook as a separate editor. As a result, when building the left- and right-contexts for the completion from the current editor, we were limited to just the current cell, which might be very small and/or reference variables and functions defined in other cells. That meant that completions never used the context of other cells when making suggestions, and were often _very_ generic. https://github.com/aws/aws-toolkit-vscode/issues/7031 ## Solution The `extractContextForCodeWhisperer` function now checks if it is being called in a cell in a Jupyter notebook. If so, it collects the surrounding cells to use as context, respecting the maximum context length. During this process, Markdown cells have each line prefixed with a language-specific comment character. --- ...-9b0e6490-39a8-445f-9d67-9d762de7421c.json | 4 + .../codewhisperer/util/editorContext.test.ts | 219 ++++++++++++++++++ .../util/runtimeLanguageContext.test.ts | 34 +++ .../src/codewhisperer/util/editorContext.ts | 125 +++++++++- .../util/runtimeLanguageContext.ts | 50 ++++ 5 files changed, 429 insertions(+), 3 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Bug Fix-9b0e6490-39a8-445f-9d67-9d762de7421c.json 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/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 59c3771abb4..a5cc430a5a9 100644 --- a/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/util/runtimeLanguageContext.test.ts @@ -333,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/core/src/codewhisperer/util/editorContext.ts b/packages/core/src/codewhisperer/util/editorContext.ts index 58301e176f6..a3f787af6c6 100644 --- a/packages/core/src/codewhisperer/util/editorContext.ts +++ b/packages/core/src/codewhisperer/util/editorContext.ts @@ -25,19 +25,125 @@ 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) @@ -48,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: { 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 From c97740e8ea85be21a42067566cf33ed30a82708c Mon Sep 17 00:00:00 2001 From: Zoe Lin <60411978+zixlin7@users.noreply.github.com> Date: Tue, 13 May 2025 10:21:34 -0700 Subject: [PATCH 152/153] feat(amazonq): import userWrittenCode configuration for inline with lsp (#7281) ## Problem Add importAdder and userWrittenCode configuration to inline with LSP ## Solution --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --- packages/amazonq/src/lsp/client.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 4af113b13c4..0b32a6b57ae 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -333,6 +333,8 @@ function getConfigSection(section: ConfigSection) { includeSuggestionsWithCodeReferences: CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled(), shareCodeWhispererContentWithAWS: !CodeWhispererSettings.instance.isOptoutEnabled(), + includeImportsWithSuggestions: CodeWhispererSettings.instance.isImportRecommendationEnabled(), + sendUserWrittenCodeMetrics: true, }, ] case 'aws.logLevel': From 143e35ced83bbe2caa96817ff0a70db7b24217fc Mon Sep 17 00:00:00 2001 From: Nikolas Komonen <118216176+nkomonen-amazon@users.noreply.github.com> Date: Tue, 13 May 2025 18:30:23 -0400 Subject: [PATCH 153/153] fix(amazonq): push customizations on startup (#7297) ## Problem At the startup of the extension, the customization that a user already decided previously was not being pushed to flare. The only time we would push the customization to flare was if the customization was changed. Otherwise everything else works as expected. ## Solution On startup, push the customization to flare (if it already exists) --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. Signed-off-by: nkomonen-amazon --- packages/amazonq/src/lsp/chat/activation.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index 9dd1d31c3de..3a36377b9b5 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -25,6 +25,11 @@ export async function activate(languageClient: LanguageClient, encryptionKey: Bu 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(), + }) const provider = new AmazonQChatViewProvider(mynahUIPath)