diff --git a/packages/amazonq/.vscode/launch.json b/packages/amazonq/.vscode/launch.json index d6ed103c42e..b00c5071ce5 100644 --- a/packages/amazonq/.vscode/launch.json +++ b/packages/amazonq/.vscode/launch.json @@ -14,6 +14,7 @@ "env": { "SSMDOCUMENT_LANGUAGESERVER_PORT": "6010", "WEBPACK_DEVELOPER_SERVER": "http://localhost:8080" + // 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" }, diff --git a/packages/amazonq/src/lsp/chat/activation.ts b/packages/amazonq/src/lsp/chat/activation.ts index a4b251ce834..3592c66fed4 100644 --- a/packages/amazonq/src/lsp/chat/activation.ts +++ b/packages/amazonq/src/lsp/chat/activation.ts @@ -8,17 +8,23 @@ import { LanguageClient } from 'vscode-languageclient' import { AmazonQChatViewProvider } from './webviewProvider' import { registerCommands } from './commands' import { registerLanguageServerEventListener, registerMessageListeners } from './messages' -import { getLogger, 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 } from 'aws-core-vscode/codewhisperer' -import { updateConfigurationRequestType } from '@aws/language-server-runtimes/protocol' +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 updateProfile(languageClient) + await pushConfigUpdate(languageClient, { + type: 'profile', + profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, + }) const provider = new AmazonQChatViewProvider(mynahUIPath) @@ -60,18 +66,48 @@ export async function activate(languageClient: LanguageClient, encryptionKey: Bu disposables.push( AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(async () => { - void updateProfile(languageClient) + 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), + }) }) ) } -async function updateProfile(client: LanguageClient) { - // update the profile on the language server - await client.sendRequest(updateConfigurationRequestType.method, { - section: 'aws.q', - settings: { - profileArn: AuthUtil.instance.regionProfileManager.activeRegionProfile?.arn, - }, - }) +/** + * 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) { + 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 }, + }) + } +} +type ProfileConfig = { + type: 'profile' + profileArn: string | undefined +} +type CustomizationConfig = { + type: 'customization' + customization: string | undefined } +type QConfigs = ProfileConfig | CustomizationConfig diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 42b621be550..b3205d009d6 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -9,7 +9,6 @@ import * as crypto from 'crypto' import { LanguageClient, LanguageClientOptions, RequestType } from 'vscode-languageclient' import { InlineCompletionManager } from '../app/inline/completion' import { AmazonQLspAuth, encryptionKey, notificationTypes } from './auth' -import { AuthUtil } from 'aws-core-vscode/codewhisperer' import { ConnectionMetadata, CreateFilesParams, @@ -22,6 +21,7 @@ import { updateConfigurationRequestType, WorkspaceFolder, } from '@aws/language-server-runtimes/protocol' +import { AuthUtil, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' import { Settings, oidcClientName, @@ -32,6 +32,8 @@ import { oneSecond, validateNodeExe, getLogger, + undefinedIfEmpty, + getOptOutPreference, } from 'aws-core-vscode/shared' import { activate } from './chat/activation' import { AmazonQResourcePaths } from './lspInstaller' @@ -74,17 +76,40 @@ export async function startLanguageServer( 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) 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: { enableLocalIndexing: true, }, }, ] } + if (params.items[0].section === 'aws.codeWhisperer') { + return [ + { + includeSuggestionsWithCodeReferences: vscode.workspace + .getConfiguration() + .get('amazonQ.showCodeWithReferences'), + shareCodeWhispererContentWithAWS: vscode.workspace + .getConfiguration() + .get('amazonQ.shareContentWithAWS'), + }, + ] + } return config }, }, diff --git a/packages/core/src/codewhisperer/util/customizationUtil.ts b/packages/core/src/codewhisperer/util/customizationUtil.ts index 7898b7a17ba..d2bcc8ce703 100644 --- a/packages/core/src/codewhisperer/util/customizationUtil.ts +++ b/packages/core/src/codewhisperer/util/customizationUtil.ts @@ -147,6 +147,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/shared/index.ts b/packages/core/src/shared/index.ts index c538d268240..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'