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: