From a2c78fb97f642b347a7db4c5b1b039a55a32bc77 Mon Sep 17 00:00:00 2001 From: hkobew Date: Mon, 5 May 2025 16:18:43 -0400 Subject: [PATCH 1/8] feat: add feature flag for inline chat --- packages/core/src/shared/settings-toolkit.gen.ts | 1 + packages/toolkit/package.json | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/core/src/shared/settings-toolkit.gen.ts b/packages/core/src/shared/settings-toolkit.gen.ts index 59a637a4870..10020cf51f9 100644 --- a/packages/core/src/shared/settings-toolkit.gen.ts +++ b/packages/core/src/shared/settings-toolkit.gen.ts @@ -44,6 +44,7 @@ export const toolkitSettings = { "jsonResourceModification": {}, "amazonqLSP": {}, "amazonqLSPInline": {}, + "amazonqLSPInlineChat": {}, "amazonqChatLSP": {} }, "aws.resources.enabledResources": {}, diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json index aa59eb2f0d0..1b1a1823164 100644 --- a/packages/toolkit/package.json +++ b/packages/toolkit/package.json @@ -255,6 +255,10 @@ "type": "boolean", "default": false }, + "amazonqLSPInlineChat": { + "type": "boolean", + "default": false + }, "amazonqChatLSP": { "type": "boolean", "default": true From a08ca021508b2887ddc0b5f9478401e2501f1511 Mon Sep 17 00:00:00 2001 From: hkobew Date: Mon, 5 May 2025 16:43:09 -0400 Subject: [PATCH 2/8] refactor: move encryption utils to seperate file --- .../controller/inlineChatController.ts | 121 +++++++++++++++++- packages/amazonq/src/lsp/chat/messages.ts | 25 +--- packages/amazonq/src/lsp/encryption.ts | 28 ++++ 3 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 packages/amazonq/src/lsp/encryption.ts diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts index 7ace8d0095e..ebd9779b8b4 100644 --- a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -23,6 +23,7 @@ import { Timeout, textDocumentUtil, isSageMaker, + Experiments, } from 'aws-core-vscode/shared' import { InlineLineAnnotationController } from '../decorations/inlineLineAnnotationController' @@ -33,6 +34,7 @@ export class InlineChatController { private readonly codeLenseProvider: CodelensProvider private readonly referenceLogController = new ReferenceLogController() private readonly inlineLineAnnotationController: InlineLineAnnotationController + private readonly computeDiffAndRenderOnEditor: (query: string) => Promise private userQuery: string | undefined private listeners: vscode.Disposable[] = [] @@ -41,6 +43,9 @@ export class InlineChatController { this.inlineChatProvider.onErrorOccured(() => this.handleError()) this.codeLenseProvider = new CodelensProvider(context) this.inlineLineAnnotationController = new InlineLineAnnotationController(context) + this.computeDiffAndRenderOnEditor = Experiments.instance.get('amazonqLSPInlineChat', false) + ? this.computeDiffAndRenderOnEditorLSP.bind(this) + : this.computeDiffAndRenderOnEditorLocal.bind(this) } public async createTask( @@ -206,7 +211,7 @@ export class InlineChatController { await textDocumentUtil.addEofNewline(editor) this.task = await this.createTask(query, editor.document, editor.selection) await this.inlineLineAnnotationController.disable(editor) - await this.computeDiffAndRenderOnEditor(query, editor.document).catch(async (err) => { + await this.computeDiffAndRenderOnEditor(query).catch(async (err) => { getLogger().error('computeDiffAndRenderOnEditor error: %s', (err as Error)?.message) if (err instanceof Error) { void vscode.window.showErrorMessage(`Amazon Q: ${err.message}`) @@ -218,7 +223,119 @@ export class InlineChatController { }) } - private async computeDiffAndRenderOnEditor(query: string, document: vscode.TextDocument) { + private async computeDiffAndRenderOnEditorLSP(query: string) { + if (!this.task) { + return + } + + await this.updateTaskAndLenses(this.task, TaskState.InProgress) + getLogger().info(`inline chat query:\n${query}`) + const uuid = randomUUID() + const message: PromptMessage = { + message: query, + messageId: uuid, + command: undefined, + userIntent: undefined, + tabID: uuid, + } + + const requestStart = performance.now() + let responseStartLatency: number | undefined + + const response = await this.inlineChatProvider.processPromptMessage(message) + this.task.requestId = response?.$metadata.requestId + + // Deselect all code + const editor = vscode.window.activeTextEditor + if (editor) { + const selection = editor.selection + if (!selection.isEmpty) { + const cursor = selection.active + const newSelection = new vscode.Selection(cursor, cursor) + editor.selection = newSelection + } + } + + if (response) { + let qSuggestedCodeResponse = '' + for await (const chatEvent of response.generateAssistantResponseResponse!) { + if ( + chatEvent.assistantResponseEvent?.content !== undefined && + chatEvent.assistantResponseEvent.content.length > 0 + ) { + if (responseStartLatency === undefined) { + responseStartLatency = performance.now() - requestStart + } + + qSuggestedCodeResponse += chatEvent.assistantResponseEvent.content + + const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, false) + if (transformedResponse) { + const textDiff = computeDiff(transformedResponse, this.task, true) + const decorations = computeDecorations(this.task) + this.task.decorations = decorations + await this.applyDiff(this.task!, textDiff ?? [], { + undoStopBefore: false, + undoStopAfter: false, + }) + this.decorator.applyDecorations(this.task) + this.task.previouseDiff = textDiff + } + } + if ( + chatEvent.codeReferenceEvent?.references !== undefined && + chatEvent.codeReferenceEvent.references.length > 0 + ) { + this.task.codeReferences = this.task.codeReferences.concat(chatEvent.codeReferenceEvent?.references) + // clear diff if user settings is off for code reference + if (!CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled()) { + await this.rejectAllChanges(this.task, false) + void vscode.window.showInformationMessage( + 'Your settings do not allow code generation with references.' + ) + await this.updateTaskAndLenses(this.task, TaskState.Complete) + return + } + } + if (chatEvent.error) { + getLogger().error('generateAssistantResponse stream error: %s', chatEvent.error) + await this.rejectAllChanges(this.task, false) + void vscode.window.showErrorMessage(`Amazon Q: ${chatEvent.error.message}`) + await this.updateTaskAndLenses(this.task, TaskState.Complete) + return + } + } + + if (this.task) { + // Unclear why we need to check if task is defined, but occasionally an error occurs otherwise + this.task.responseStartLatency = responseStartLatency + this.task.responseEndLatency = performance.now() - requestStart + } + getLogger().info(`qSuggestedCodeResponse:\n${qSuggestedCodeResponse}`) + const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, true) + if (transformedResponse) { + const textDiff = computeDiff(transformedResponse, this.task, false) + const decorations = computeDecorations(this.task) + this.task.decorations = decorations + await this.applyDiff(this.task, textDiff ?? []) + this.decorator.applyDecorations(this.task) + await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision) + await setContext('amazonq.inline.codelensShortcutEnabled', true) + this.undoListener(this.task) + } else { + void messages.showMessageWithCancel( + 'No suggestions from Q, please try different instructions.', + new Timeout(5000) + ) + await this.updateTaskAndLenses(this.task, TaskState.Complete) + await this.inlineQuickPick(this.userQuery) + await this.handleError() + } + } + } + + // TODO: remove this implementation in favor or LSP + private async computeDiffAndRenderOnEditorLocal(query: string) { if (!this.task) { return } diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 9578858b708..3236ec53295 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -55,7 +55,6 @@ import { 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, ReferenceLogViewProvider } from 'aws-core-vscode/codewhisperer' import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl } from 'aws-core-vscode/shared' @@ -68,6 +67,7 @@ import { } from 'aws-core-vscode/amazonq' import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry' import { isValidResponseError } from './error' +import { decodeRequest, encryptRequest } from '../encryption' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -487,29 +487,6 @@ function isServerEvent(command: string) { return command.startsWith('aws/chat/') || command === 'telemetry/event' } -async function encryptRequest(params: T, encryptionKey: Buffer): Promise<{ message: string } | T> { - const payload = new TextEncoder().encode(JSON.stringify(params)) - - const encryptedMessage = await new jose.CompactEncrypt(payload) - .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' }) - .encrypt(encryptionKey) - - return { message: encryptedMessage } -} - -async function decodeRequest(request: string, key: Buffer): Promise { - const result = await jose.jwtDecrypt(request, key, { - clockTolerance: 60, // Allow up to 60 seconds to account for clock differences - contentEncryptionAlgorithms: ['A256GCM'], - keyManagementAlgorithms: ['dir'], - }) - - if (!result.payload) { - throw new Error('JWT payload not found') - } - return result.payload as T -} - /** * Decodes partial chat responses from the language server before sending them to mynah UI */ diff --git a/packages/amazonq/src/lsp/encryption.ts b/packages/amazonq/src/lsp/encryption.ts new file mode 100644 index 00000000000..213ee3c1553 --- /dev/null +++ b/packages/amazonq/src/lsp/encryption.ts @@ -0,0 +1,28 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as jose from 'jose' + +export async function encryptRequest(params: T, encryptionKey: Buffer): Promise<{ message: string } | T> { + const payload = new TextEncoder().encode(JSON.stringify(params)) + + const encryptedMessage = await new jose.CompactEncrypt(payload) + .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' }) + .encrypt(encryptionKey) + + return { message: encryptedMessage } +} + +export async function decodeRequest(request: string, key: Buffer): Promise { + const result = await jose.jwtDecrypt(request, key, { + clockTolerance: 60, // Allow up to 60 seconds to account for clock differences + contentEncryptionAlgorithms: ['A256GCM'], + keyManagementAlgorithms: ['dir'], + }) + + if (!result.payload) { + throw new Error('JWT payload not found') + } + return result.payload as T +} From 2d4e6f645308a42c0ac66806db4012545ef40675 Mon Sep 17 00:00:00 2001 From: hkobew Date: Tue, 6 May 2025 12:51:59 -0400 Subject: [PATCH 3/8] refactor: move cursorPosition util to seperate file --- packages/amazonq/src/lsp/chat/messages.ts | 16 +--------------- packages/amazonq/src/lsp/utils.ts | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 packages/amazonq/src/lsp/utils.ts diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 3236ec53295..93ede65fc9a 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -68,6 +68,7 @@ import { import { telemetry, TelemetryBase } from 'aws-core-vscode/telemetry' import { isValidResponseError } from './error' import { decodeRequest, encryptRequest } from '../encryption' +import { getCursorState } from '../utils' export function registerLanguageServerEventListener(languageClient: LanguageClient, provider: AmazonQChatViewProvider) { languageClient.info( @@ -99,21 +100,6 @@ export function registerLanguageServerEventListener(languageClient: LanguageClie }) } -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, diff --git a/packages/amazonq/src/lsp/utils.ts b/packages/amazonq/src/lsp/utils.ts new file mode 100644 index 00000000000..39dcf9b50cf --- /dev/null +++ b/packages/amazonq/src/lsp/utils.ts @@ -0,0 +1,20 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import * as vscode from 'vscode' + +export 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, + }, + }, + })) +} From 47d98ca31a16db52143673bc3859747ab511f6f1 Mon Sep 17 00:00:00 2001 From: hkobew Date: Tue, 6 May 2025 13:27:19 -0400 Subject: [PATCH 4/8] feat: working e2e flow of inline chat through language server --- packages/amazonq/src/extensionNode.ts | 2 - packages/amazonq/src/inlineChat/activation.ts | 5 +- .../controller/inlineChatController.ts | 111 +++--------------- .../inlineChat/provider/inlineChatProvider.ts | 45 ++++++- packages/amazonq/src/lsp/client.ts | 3 + 5 files changed, 68 insertions(+), 98 deletions(-) diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index 8224b9ce310..d42fafea058 100644 --- a/packages/amazonq/src/extensionNode.ts +++ b/packages/amazonq/src/extensionNode.ts @@ -25,7 +25,6 @@ import { DevOptions } from 'aws-core-vscode/dev' import { Auth, AuthUtils, getTelemetryMetadataForConn, isAnySsoConnection } from 'aws-core-vscode/auth' 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 { activate as activateNotifications, NotificationsController } from 'aws-core-vscode/notifications' import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer' @@ -73,7 +72,6 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) { } activateAgents() await activateTransformationHub(extContext as ExtContext) - activateInlineChat(context) const authProvider = new CommonAuthViewProvider( context, diff --git a/packages/amazonq/src/inlineChat/activation.ts b/packages/amazonq/src/inlineChat/activation.ts index a42dfdb3e02..01e9f420c05 100644 --- a/packages/amazonq/src/inlineChat/activation.ts +++ b/packages/amazonq/src/inlineChat/activation.ts @@ -5,8 +5,9 @@ import * as vscode from 'vscode' import { InlineChatController } from './controller/inlineChatController' import { registerInlineCommands } from './command/registerInlineCommands' +import { LanguageClient } from 'vscode-languageclient' -export function activate(context: vscode.ExtensionContext) { - const inlineChatController = new InlineChatController(context) +export function activate(context: vscode.ExtensionContext, client: LanguageClient, encryptionKey: Buffer) { + const inlineChatController = new InlineChatController(context, client, encryptionKey) registerInlineCommands(context, inlineChatController) } diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts index ebd9779b8b4..4c2f9fee62c 100644 --- a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -14,6 +14,7 @@ import { CodelensProvider } from '../codeLenses/codeLenseProvider' import { PromptMessage, ReferenceLogController } from 'aws-core-vscode/codewhispererChat' import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer' import { UserWrittenCodeTracker } from 'aws-core-vscode/codewhisperer' +import { LanguageClient } from 'vscode-languageclient' import { codicon, getIcon, @@ -38,8 +39,8 @@ export class InlineChatController { private userQuery: string | undefined private listeners: vscode.Disposable[] = [] - constructor(context: vscode.ExtensionContext) { - this.inlineChatProvider = new InlineChatProvider() + constructor(context: vscode.ExtensionContext, client: LanguageClient, encryptionKey: Buffer) { + this.inlineChatProvider = new InlineChatProvider(client, encryptionKey) this.inlineChatProvider.onErrorOccured(() => this.handleError()) this.codeLenseProvider = new CodelensProvider(context) this.inlineLineAnnotationController = new InlineLineAnnotationController(context) @@ -239,102 +240,26 @@ export class InlineChatController { tabID: uuid, } - const requestStart = performance.now() - let responseStartLatency: number | undefined - - const response = await this.inlineChatProvider.processPromptMessage(message) - this.task.requestId = response?.$metadata.requestId + const response = await this.inlineChatProvider.processPromptMessageLSP(message) - // Deselect all code - const editor = vscode.window.activeTextEditor - if (editor) { - const selection = editor.selection - if (!selection.isEmpty) { - const cursor = selection.active - const newSelection = new vscode.Selection(cursor, cursor) - editor.selection = newSelection - } + // TODO: add tests for this case. + if (!response.body) { + getLogger().warn('Empty body in inline chat response') + await this.handleError() + return } - if (response) { - let qSuggestedCodeResponse = '' - for await (const chatEvent of response.generateAssistantResponseResponse!) { - if ( - chatEvent.assistantResponseEvent?.content !== undefined && - chatEvent.assistantResponseEvent.content.length > 0 - ) { - if (responseStartLatency === undefined) { - responseStartLatency = performance.now() - requestStart - } - - qSuggestedCodeResponse += chatEvent.assistantResponseEvent.content - - const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, false) - if (transformedResponse) { - const textDiff = computeDiff(transformedResponse, this.task, true) - const decorations = computeDecorations(this.task) - this.task.decorations = decorations - await this.applyDiff(this.task!, textDiff ?? [], { - undoStopBefore: false, - undoStopAfter: false, - }) - this.decorator.applyDecorations(this.task) - this.task.previouseDiff = textDiff - } - } - if ( - chatEvent.codeReferenceEvent?.references !== undefined && - chatEvent.codeReferenceEvent.references.length > 0 - ) { - this.task.codeReferences = this.task.codeReferences.concat(chatEvent.codeReferenceEvent?.references) - // clear diff if user settings is off for code reference - if (!CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled()) { - await this.rejectAllChanges(this.task, false) - void vscode.window.showInformationMessage( - 'Your settings do not allow code generation with references.' - ) - await this.updateTaskAndLenses(this.task, TaskState.Complete) - return - } - } - if (chatEvent.error) { - getLogger().error('generateAssistantResponse stream error: %s', chatEvent.error) - await this.rejectAllChanges(this.task, false) - void vscode.window.showErrorMessage(`Amazon Q: ${chatEvent.error.message}`) - await this.updateTaskAndLenses(this.task, TaskState.Complete) - return - } - } - - if (this.task) { - // Unclear why we need to check if task is defined, but occasionally an error occurs otherwise - this.task.responseStartLatency = responseStartLatency - this.task.responseEndLatency = performance.now() - requestStart - } - getLogger().info(`qSuggestedCodeResponse:\n${qSuggestedCodeResponse}`) - const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, true) - if (transformedResponse) { - const textDiff = computeDiff(transformedResponse, this.task, false) - const decorations = computeDecorations(this.task) - this.task.decorations = decorations - await this.applyDiff(this.task, textDiff ?? []) - this.decorator.applyDecorations(this.task) - await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision) - await setContext('amazonq.inline.codelensShortcutEnabled', true) - this.undoListener(this.task) - } else { - void messages.showMessageWithCancel( - 'No suggestions from Q, please try different instructions.', - new Timeout(5000) - ) - await this.updateTaskAndLenses(this.task, TaskState.Complete) - await this.inlineQuickPick(this.userQuery) - await this.handleError() - } - } + const textDiff = computeDiff(response.body, this.task, false) + const decorations = computeDecorations(this.task) + this.task.decorations = decorations + await this.applyDiff(this.task, textDiff ?? []) + this.decorator.applyDecorations(this.task) + await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision) + await setContext('amazonq.inline.codelensShortcutEnabled', true) + this.undoListener(this.task) } - // TODO: remove this implementation in favor or LSP + // TODO: remove this implementation in favor of LSP private async computeDiffAndRenderOnEditorLocal(query: string) { if (!this.task) { return diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts index e6534d65532..fd0a4e04b6e 100644 --- a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -8,6 +8,8 @@ import { CodeWhispererStreamingServiceException, GenerateAssistantResponseCommandOutput, } from '@amzn/codewhisperer-streaming' +import { LanguageClient } from 'vscode-languageclient' +import { inlineChatRequestType } from '@aws/language-server-runtimes/protocol' import { AuthUtil, getSelectedCustomization } from 'aws-core-vscode/codewhisperer' import { ChatSessionStorage, @@ -25,6 +27,9 @@ import { codeWhispererClient } from 'aws-core-vscode/codewhisperer' import type { InlineChatEvent } from 'aws-core-vscode/codewhisperer' import { InlineTask } from '../controller/inlineTask' import { extractAuthFollowUp } from 'aws-core-vscode/amazonq' +import { InlineChatParams, InlineChatResult } from '@aws/language-server-runtimes-types' +import { decodeRequest, encryptRequest } from '../../lsp/encryption' +import { getCursorState } from '../../lsp/utils' export class InlineChatProvider { private readonly editorContextExtractor: EditorContextExtractor @@ -34,13 +39,51 @@ export class InlineChatProvider { private errorEmitter = new vscode.EventEmitter() public onErrorOccured = this.errorEmitter.event - public constructor() { + public constructor( + private readonly client: LanguageClient, + private readonly encryptionKey: Buffer + ) { this.editorContextExtractor = new EditorContextExtractor() this.userIntentRecognizer = new UserIntentRecognizer() this.sessionStorage = new ChatSessionStorage() this.triggerEventsStorage = new TriggerEventsStorage() } + private getCurrentEditorParams(prompt: string): InlineChatParams { + const editor = vscode.window.activeTextEditor + if (!editor) { + throw new ToolkitError('No active editor') + } + + const documentUri = editor.document.uri.toString() + + return { + prompt: { + prompt, + }, + cursorState: getCursorState(editor.selections), + textDocument: { + uri: documentUri, + }, + } + } + + public async processPromptMessageLSP(message: PromptMessage): Promise { + getLogger().info('Making inline chat request with message %O', message) + const params = this.getCurrentEditorParams(message.message ?? '') + const inlineChatRequest = await encryptRequest(params, this.encryptionKey) + const response = await this.client.sendRequest(inlineChatRequestType.method, inlineChatRequest) + const decryptedMessage = + typeof response === 'string' && this.encryptionKey + ? await decodeRequest(response, this.encryptionKey) + : response + const result: InlineChatResult = decryptedMessage as InlineChatResult + this.client.info(`Logging response for inline chat ${JSON.stringify(decryptedMessage)}`) + + return result + } + + // TODO: remove in favor of LSP implementation. public async processPromptMessage(message: PromptMessage) { return this.editorContextExtractor .extractContextForTrigger('ChatMessage') diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index a8cb8d76a40..d6a6e0b5974 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 { activate as activateInline } from '../inlineChat/activation' const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') @@ -182,6 +183,8 @@ export async function startLanguageServer( await activate(client, encryptionKey, resourcePaths.ui) } + activateInline(extensionContext, client, encryptionKey) + const refreshInterval = auth.startTokenRefreshInterval(10 * oneSecond) const sendProfileToLsp = async () => { From 9647ba86ab13f762489282051b057befe9d4caa7 Mon Sep 17 00:00:00 2001 From: hkobew Date: Tue, 6 May 2025 15:39:54 -0400 Subject: [PATCH 5/8] docs: add comments for missing features --- .../amazonq/src/inlineChat/controller/inlineChatController.ts | 3 +++ packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts index 4c2f9fee62c..4eb7c0a7c26 100644 --- a/packages/amazonq/src/inlineChat/controller/inlineChatController.ts +++ b/packages/amazonq/src/inlineChat/controller/inlineChatController.ts @@ -249,11 +249,14 @@ export class InlineChatController { return } + // Update inline diff view const textDiff = computeDiff(response.body, this.task, false) const decorations = computeDecorations(this.task) this.task.decorations = decorations await this.applyDiff(this.task, textDiff ?? []) this.decorator.applyDecorations(this.task) + + // Update Codelenses await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision) await setContext('amazonq.inline.codelensShortcutEnabled', true) this.undoListener(this.task) diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts index fd0a4e04b6e..d2e82917f87 100644 --- a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -69,6 +69,7 @@ export class InlineChatProvider { } public async processPromptMessageLSP(message: PromptMessage): Promise { + // TODO: handle partial responses. getLogger().info('Making inline chat request with message %O', message) const params = this.getCurrentEditorParams(message.message ?? '') const inlineChatRequest = await encryptRequest(params, this.encryptionKey) From 53671c32c1fc3ff90d34762ed234b641fd3e156b Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 7 May 2025 08:36:22 -0400 Subject: [PATCH 6/8] refactor: slight cleanup --- .../amazonq/src/inlineChat/provider/inlineChatProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts index d2e82917f87..86fe0ac2ade 100644 --- a/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts +++ b/packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts @@ -56,12 +56,12 @@ export class InlineChatProvider { } const documentUri = editor.document.uri.toString() - + const cursorState = getCursorState(editor.selections) return { prompt: { prompt, }, - cursorState: getCursorState(editor.selections), + cursorState, textDocument: { uri: documentUri, }, From 360de616bd19d55dacdf9a684a4c2ccd9772373c Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 7 May 2025 10:28:50 -0400 Subject: [PATCH 7/8] docs: add comment clarifying util is lsp specific --- packages/amazonq/src/lsp/utils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/utils.ts b/packages/amazonq/src/lsp/utils.ts index 39dcf9b50cf..f5b010c536b 100644 --- a/packages/amazonq/src/lsp/utils.ts +++ b/packages/amazonq/src/lsp/utils.ts @@ -3,8 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ import * as vscode from 'vscode' +import { CursorState } from '@aws/language-server-runtimes-types' -export function getCursorState(selection: readonly vscode.Selection[]) { +/** + * Convert from vscode selection type to the general CursorState expected by the AmazonQLSP. + * @param selection + * @returns + */ +export function getCursorState(selection: readonly vscode.Selection[]): CursorState[] { return selection.map((s) => ({ range: { start: { From 9e83cac03c58eeb17fbd747662388cca38e4c041 Mon Sep 17 00:00:00 2001 From: hkobew Date: Wed, 7 May 2025 10:33:34 -0400 Subject: [PATCH 8/8] refactor: rename activation function to be inlineChat specific --- 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 d6a6e0b5974..2db8ab43b0c 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -39,7 +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 { activate as activateInline } from '../inlineChat/activation' +import { activate as activateInlineChat } from '../inlineChat/activation' const localize = nls.loadMessageBundle() const logger = getLogger('amazonqLsp.lspClient') @@ -183,7 +183,7 @@ export async function startLanguageServer( await activate(client, encryptionKey, resourcePaths.ui) } - activateInline(extensionContext, client, encryptionKey) + activateInlineChat(extensionContext, client, encryptionKey) const refreshInterval = auth.startTokenRefreshInterval(10 * oneSecond)