Skip to content

Commit 47d98ca

Browse files
committed
feat: working e2e flow of inline chat through language server
1 parent 2d4e6f6 commit 47d98ca

File tree

5 files changed

+68
-98
lines changed

5 files changed

+68
-98
lines changed

packages/amazonq/src/extensionNode.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { DevOptions } from 'aws-core-vscode/dev'
2525
import { Auth, AuthUtils, getTelemetryMetadataForConn, isAnySsoConnection } from 'aws-core-vscode/auth'
2626
import api from './api'
2727
import { activate as activateCWChat } from './app/chat/activation'
28-
import { activate as activateInlineChat } from './inlineChat/activation'
2928
import { beta } from 'aws-core-vscode/dev'
3029
import { activate as activateNotifications, NotificationsController } from 'aws-core-vscode/notifications'
3130
import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer'
@@ -73,7 +72,6 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) {
7372
}
7473
activateAgents()
7574
await activateTransformationHub(extContext as ExtContext)
76-
activateInlineChat(context)
7775

7876
const authProvider = new CommonAuthViewProvider(
7977
context,

packages/amazonq/src/inlineChat/activation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import * as vscode from 'vscode'
66
import { InlineChatController } from './controller/inlineChatController'
77
import { registerInlineCommands } from './command/registerInlineCommands'
8+
import { LanguageClient } from 'vscode-languageclient'
89

9-
export function activate(context: vscode.ExtensionContext) {
10-
const inlineChatController = new InlineChatController(context)
10+
export function activate(context: vscode.ExtensionContext, client: LanguageClient, encryptionKey: Buffer) {
11+
const inlineChatController = new InlineChatController(context, client, encryptionKey)
1112
registerInlineCommands(context, inlineChatController)
1213
}

packages/amazonq/src/inlineChat/controller/inlineChatController.ts

Lines changed: 18 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { CodelensProvider } from '../codeLenses/codeLenseProvider'
1414
import { PromptMessage, ReferenceLogController } from 'aws-core-vscode/codewhispererChat'
1515
import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer'
1616
import { UserWrittenCodeTracker } from 'aws-core-vscode/codewhisperer'
17+
import { LanguageClient } from 'vscode-languageclient'
1718
import {
1819
codicon,
1920
getIcon,
@@ -38,8 +39,8 @@ export class InlineChatController {
3839
private userQuery: string | undefined
3940
private listeners: vscode.Disposable[] = []
4041

41-
constructor(context: vscode.ExtensionContext) {
42-
this.inlineChatProvider = new InlineChatProvider()
42+
constructor(context: vscode.ExtensionContext, client: LanguageClient, encryptionKey: Buffer) {
43+
this.inlineChatProvider = new InlineChatProvider(client, encryptionKey)
4344
this.inlineChatProvider.onErrorOccured(() => this.handleError())
4445
this.codeLenseProvider = new CodelensProvider(context)
4546
this.inlineLineAnnotationController = new InlineLineAnnotationController(context)
@@ -239,102 +240,26 @@ export class InlineChatController {
239240
tabID: uuid,
240241
}
241242

242-
const requestStart = performance.now()
243-
let responseStartLatency: number | undefined
244-
245-
const response = await this.inlineChatProvider.processPromptMessage(message)
246-
this.task.requestId = response?.$metadata.requestId
243+
const response = await this.inlineChatProvider.processPromptMessageLSP(message)
247244

248-
// Deselect all code
249-
const editor = vscode.window.activeTextEditor
250-
if (editor) {
251-
const selection = editor.selection
252-
if (!selection.isEmpty) {
253-
const cursor = selection.active
254-
const newSelection = new vscode.Selection(cursor, cursor)
255-
editor.selection = newSelection
256-
}
245+
// TODO: add tests for this case.
246+
if (!response.body) {
247+
getLogger().warn('Empty body in inline chat response')
248+
await this.handleError()
249+
return
257250
}
258251

259-
if (response) {
260-
let qSuggestedCodeResponse = ''
261-
for await (const chatEvent of response.generateAssistantResponseResponse!) {
262-
if (
263-
chatEvent.assistantResponseEvent?.content !== undefined &&
264-
chatEvent.assistantResponseEvent.content.length > 0
265-
) {
266-
if (responseStartLatency === undefined) {
267-
responseStartLatency = performance.now() - requestStart
268-
}
269-
270-
qSuggestedCodeResponse += chatEvent.assistantResponseEvent.content
271-
272-
const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, false)
273-
if (transformedResponse) {
274-
const textDiff = computeDiff(transformedResponse, this.task, true)
275-
const decorations = computeDecorations(this.task)
276-
this.task.decorations = decorations
277-
await this.applyDiff(this.task!, textDiff ?? [], {
278-
undoStopBefore: false,
279-
undoStopAfter: false,
280-
})
281-
this.decorator.applyDecorations(this.task)
282-
this.task.previouseDiff = textDiff
283-
}
284-
}
285-
if (
286-
chatEvent.codeReferenceEvent?.references !== undefined &&
287-
chatEvent.codeReferenceEvent.references.length > 0
288-
) {
289-
this.task.codeReferences = this.task.codeReferences.concat(chatEvent.codeReferenceEvent?.references)
290-
// clear diff if user settings is off for code reference
291-
if (!CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled()) {
292-
await this.rejectAllChanges(this.task, false)
293-
void vscode.window.showInformationMessage(
294-
'Your settings do not allow code generation with references.'
295-
)
296-
await this.updateTaskAndLenses(this.task, TaskState.Complete)
297-
return
298-
}
299-
}
300-
if (chatEvent.error) {
301-
getLogger().error('generateAssistantResponse stream error: %s', chatEvent.error)
302-
await this.rejectAllChanges(this.task, false)
303-
void vscode.window.showErrorMessage(`Amazon Q: ${chatEvent.error.message}`)
304-
await this.updateTaskAndLenses(this.task, TaskState.Complete)
305-
return
306-
}
307-
}
308-
309-
if (this.task) {
310-
// Unclear why we need to check if task is defined, but occasionally an error occurs otherwise
311-
this.task.responseStartLatency = responseStartLatency
312-
this.task.responseEndLatency = performance.now() - requestStart
313-
}
314-
getLogger().info(`qSuggestedCodeResponse:\n${qSuggestedCodeResponse}`)
315-
const transformedResponse = responseTransformer(qSuggestedCodeResponse, this.task, true)
316-
if (transformedResponse) {
317-
const textDiff = computeDiff(transformedResponse, this.task, false)
318-
const decorations = computeDecorations(this.task)
319-
this.task.decorations = decorations
320-
await this.applyDiff(this.task, textDiff ?? [])
321-
this.decorator.applyDecorations(this.task)
322-
await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision)
323-
await setContext('amazonq.inline.codelensShortcutEnabled', true)
324-
this.undoListener(this.task)
325-
} else {
326-
void messages.showMessageWithCancel(
327-
'No suggestions from Q, please try different instructions.',
328-
new Timeout(5000)
329-
)
330-
await this.updateTaskAndLenses(this.task, TaskState.Complete)
331-
await this.inlineQuickPick(this.userQuery)
332-
await this.handleError()
333-
}
334-
}
252+
const textDiff = computeDiff(response.body, this.task, false)
253+
const decorations = computeDecorations(this.task)
254+
this.task.decorations = decorations
255+
await this.applyDiff(this.task, textDiff ?? [])
256+
this.decorator.applyDecorations(this.task)
257+
await this.updateTaskAndLenses(this.task, TaskState.WaitingForDecision)
258+
await setContext('amazonq.inline.codelensShortcutEnabled', true)
259+
this.undoListener(this.task)
335260
}
336261

337-
// TODO: remove this implementation in favor or LSP
262+
// TODO: remove this implementation in favor of LSP
338263
private async computeDiffAndRenderOnEditorLocal(query: string) {
339264
if (!this.task) {
340265
return

packages/amazonq/src/inlineChat/provider/inlineChatProvider.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
CodeWhispererStreamingServiceException,
99
GenerateAssistantResponseCommandOutput,
1010
} from '@amzn/codewhisperer-streaming'
11+
import { LanguageClient } from 'vscode-languageclient'
12+
import { inlineChatRequestType } from '@aws/language-server-runtimes/protocol'
1113
import { AuthUtil, getSelectedCustomization } from 'aws-core-vscode/codewhisperer'
1214
import {
1315
ChatSessionStorage,
@@ -25,6 +27,9 @@ import { codeWhispererClient } from 'aws-core-vscode/codewhisperer'
2527
import type { InlineChatEvent } from 'aws-core-vscode/codewhisperer'
2628
import { InlineTask } from '../controller/inlineTask'
2729
import { extractAuthFollowUp } from 'aws-core-vscode/amazonq'
30+
import { InlineChatParams, InlineChatResult } from '@aws/language-server-runtimes-types'
31+
import { decodeRequest, encryptRequest } from '../../lsp/encryption'
32+
import { getCursorState } from '../../lsp/utils'
2833

2934
export class InlineChatProvider {
3035
private readonly editorContextExtractor: EditorContextExtractor
@@ -34,13 +39,51 @@ export class InlineChatProvider {
3439
private errorEmitter = new vscode.EventEmitter<void>()
3540
public onErrorOccured = this.errorEmitter.event
3641

37-
public constructor() {
42+
public constructor(
43+
private readonly client: LanguageClient,
44+
private readonly encryptionKey: Buffer
45+
) {
3846
this.editorContextExtractor = new EditorContextExtractor()
3947
this.userIntentRecognizer = new UserIntentRecognizer()
4048
this.sessionStorage = new ChatSessionStorage()
4149
this.triggerEventsStorage = new TriggerEventsStorage()
4250
}
4351

52+
private getCurrentEditorParams(prompt: string): InlineChatParams {
53+
const editor = vscode.window.activeTextEditor
54+
if (!editor) {
55+
throw new ToolkitError('No active editor')
56+
}
57+
58+
const documentUri = editor.document.uri.toString()
59+
60+
return {
61+
prompt: {
62+
prompt,
63+
},
64+
cursorState: getCursorState(editor.selections),
65+
textDocument: {
66+
uri: documentUri,
67+
},
68+
}
69+
}
70+
71+
public async processPromptMessageLSP(message: PromptMessage): Promise<InlineChatResult> {
72+
getLogger().info('Making inline chat request with message %O', message)
73+
const params = this.getCurrentEditorParams(message.message ?? '')
74+
const inlineChatRequest = await encryptRequest<InlineChatParams>(params, this.encryptionKey)
75+
const response = await this.client.sendRequest(inlineChatRequestType.method, inlineChatRequest)
76+
const decryptedMessage =
77+
typeof response === 'string' && this.encryptionKey
78+
? await decodeRequest(response, this.encryptionKey)
79+
: response
80+
const result: InlineChatResult = decryptedMessage as InlineChatResult
81+
this.client.info(`Logging response for inline chat ${JSON.stringify(decryptedMessage)}`)
82+
83+
return result
84+
}
85+
86+
// TODO: remove in favor of LSP implementation.
4487
public async processPromptMessage(message: PromptMessage) {
4588
return this.editorContextExtractor
4689
.extractContextForTrigger('ChatMessage')

packages/amazonq/src/lsp/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { processUtils } from 'aws-core-vscode/shared'
3939
import { activate } from './chat/activation'
4040
import { AmazonQResourcePaths } from './lspInstaller'
4141
import { ConfigSection, isValidConfigSection, toAmazonQLSPLogLevel } from './config'
42+
import { activate as activateInline } from '../inlineChat/activation'
4243

4344
const localize = nls.loadMessageBundle()
4445
const logger = getLogger('amazonqLsp.lspClient')
@@ -182,6 +183,8 @@ export async function startLanguageServer(
182183
await activate(client, encryptionKey, resourcePaths.ui)
183184
}
184185

186+
activateInline(extensionContext, client, encryptionKey)
187+
185188
const refreshInterval = auth.startTokenRefreshInterval(10 * oneSecond)
186189

187190
const sendProfileToLsp = async () => {

0 commit comments

Comments
 (0)