Skip to content

Commit d0a41e8

Browse files
Merge master into feature/q-dev-ux
2 parents 4d4ad6d + fb62929 commit d0a41e8

File tree

11 files changed

+228
-85
lines changed

11 files changed

+228
-85
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Update `@workspace` index when adding or deleting a file"
4+
}

packages/amazonq/src/app/chat/activation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export async function activate(context: ExtensionContext) {
2525
await amazonq.TryChatCodeLensProvider.register(appInitContext.onDidChangeAmazonQVisibility.event)
2626

2727
const setupLsp = funcUtil.debounce(async () => {
28-
void amazonq.LspController.instance.trySetupLsp(context)
28+
void amazonq.LspController.instance.trySetupLsp(context, {
29+
startUrl: AuthUtil.instance.startUrl,
30+
maxIndexSize: CodeWhispererSettings.instance.getMaxIndexSize(),
31+
isVectorIndexEnabled: CodeWhispererSettings.instance.isLocalIndexEnabled(),
32+
})
2933
}, 5000)
3034

3135
context.subscriptions.push(

packages/amazonq/test/unit/amazonq/lsp/lspClient.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('Amazon Q LSP client', function () {
1818
})
1919

2020
it('encrypts payload of query ', async () => {
21-
await lspClient.query('mock_input')
21+
await lspClient.queryVectorIndex('mock_input')
2222
assert.ok(encryptFunc.calledOnce)
2323
assert.ok(encryptFunc.calledWith(JSON.stringify({ query: 'mock_input' })))
2424
const value = await encryptFunc.returnValues[0]
@@ -27,14 +27,15 @@ describe('Amazon Q LSP client', function () {
2727
})
2828

2929
it('encrypts payload of index files ', async () => {
30-
await lspClient.indexFiles(['fileA'], 'path', false)
30+
await lspClient.buildIndex(['fileA'], 'path', 'all')
3131
assert.ok(encryptFunc.calledOnce)
3232
assert.ok(
3333
encryptFunc.calledWith(
3434
JSON.stringify({
3535
filePaths: ['fileA'],
36-
rootPath: 'path',
37-
refresh: false,
36+
projectRoot: 'path',
37+
config: 'all',
38+
language: '',
3839
})
3940
)
4041
)

packages/core/src/amazonq/lsp/lspClient.ts

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ import * as jose from 'jose'
1717
import { Disposable, ExtensionContext } from 'vscode'
1818

1919
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'
20-
import { GetUsageRequestType, IndexRequestType, QueryRequestType, UpdateIndexRequestType, Usage } from './types'
20+
import {
21+
BuildIndexRequestPayload,
22+
BuildIndexRequestType,
23+
GetUsageRequestType,
24+
IndexConfig,
25+
QueryInlineProjectContextRequestType,
26+
QueryVectorIndexRequestType,
27+
UpdateIndexV2RequestPayload,
28+
UpdateIndexV2RequestType,
29+
Usage,
30+
} from './types'
2131
import { Writable } from 'stream'
2232
import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings'
2333
import { fs, getLogger } from '../../shared'
@@ -61,52 +71,67 @@ export class LspClient {
6171
.encrypt(key)
6272
}
6373

64-
async indexFiles(request: string[], rootPath: string, refresh: boolean) {
74+
async buildIndex(paths: string[], rootPath: string, config: IndexConfig) {
75+
const payload: BuildIndexRequestPayload = {
76+
filePaths: paths,
77+
projectRoot: rootPath,
78+
config: config,
79+
language: '',
80+
}
6581
try {
66-
const encryptedRequest = await this.encrypt(
67-
JSON.stringify({
68-
filePaths: request,
69-
rootPath: rootPath,
70-
refresh: refresh,
71-
})
72-
)
73-
const resp = await this.client?.sendRequest(IndexRequestType, encryptedRequest)
82+
const encryptedRequest = await this.encrypt(JSON.stringify(payload))
83+
const resp = await this.client?.sendRequest(BuildIndexRequestType, encryptedRequest)
7484
return resp
7585
} catch (e) {
76-
getLogger().error(`LspClient: indexFiles error: ${e}`)
86+
getLogger().error(`LspClient: buildIndex error: ${e}`)
7787
return undefined
7888
}
7989
}
8090

81-
async query(request: string) {
91+
async queryVectorIndex(request: string) {
8292
try {
8393
const encryptedRequest = await this.encrypt(
8494
JSON.stringify({
8595
query: request,
8696
})
8797
)
88-
const resp = await this.client?.sendRequest(QueryRequestType, encryptedRequest)
98+
const resp = await this.client?.sendRequest(QueryVectorIndexRequestType, encryptedRequest)
8999
return resp
90100
} catch (e) {
91-
getLogger().error(`LspClient: query error: ${e}`)
101+
getLogger().error(`LspClient: queryVectorIndex error: ${e}`)
92102
return []
93103
}
94104
}
95105

106+
async queryInlineProjectContext(query: string, path: string) {
107+
try {
108+
const request = JSON.stringify({
109+
query: query,
110+
filePath: path,
111+
})
112+
const encrypted = await this.encrypt(request)
113+
const resp: any = await this.client?.sendRequest(QueryInlineProjectContextRequestType, encrypted)
114+
return resp
115+
} catch (e) {
116+
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
117+
throw e
118+
}
119+
}
120+
96121
async getLspServerUsage(): Promise<Usage | undefined> {
97122
if (this.client) {
98123
return (await this.client.sendRequest(GetUsageRequestType, '')) as Usage
99124
}
100125
}
101126

102-
async updateIndex(filePath: string) {
127+
async updateIndex(filePath: string[], mode: 'update' | 'remove' | 'add') {
128+
const payload: UpdateIndexV2RequestPayload = {
129+
filePaths: filePath,
130+
updateMode: mode,
131+
}
103132
try {
104-
const encryptedRequest = await this.encrypt(
105-
JSON.stringify({
106-
filePath: filePath,
107-
})
108-
)
109-
const resp = await this.client?.sendRequest(UpdateIndexRequestType, encryptedRequest)
133+
const encryptedRequest = await this.encrypt(JSON.stringify(payload))
134+
const resp = await this.client?.sendRequest(UpdateIndexV2RequestType, encryptedRequest)
110135
return resp
111136
} catch (e) {
112137
getLogger().error(`LspClient: updateIndex error: ${e}`)
@@ -197,15 +222,26 @@ export async function activate(extensionContext: ExtensionContext) {
197222
return
198223
}
199224
savedDocument = document.uri
200-
})
201-
)
202-
toDispose.push(
225+
}),
203226
vscode.window.onDidChangeActiveTextEditor((editor) => {
204227
if (savedDocument && editor && editor.document.uri.fsPath !== savedDocument.fsPath) {
205-
void LspClient.instance.updateIndex(savedDocument.fsPath)
228+
void LspClient.instance.updateIndex([savedDocument.fsPath], 'update')
206229
}
230+
}),
231+
vscode.workspace.onDidCreateFiles((e) => {
232+
void LspClient.instance.updateIndex(
233+
e.files.map((f) => f.fsPath),
234+
'add'
235+
)
236+
}),
237+
vscode.workspace.onDidDeleteFiles((e) => {
238+
void LspClient.instance.updateIndex(
239+
e.files.map((f) => f.fsPath),
240+
'remove'
241+
)
207242
})
208243
)
244+
209245
return LspClient.instance.client.onReady().then(() => {
210246
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
211247
toDispose.push(disposableFunc)

packages/core/src/amazonq/lsp/lspController.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@ import { LspClient } from './lspClient'
1515
import AdmZip from 'adm-zip'
1616
import { RelevantTextDocument } from '@amzn/codewhisperer-streaming'
1717
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
18-
import { CodeWhispererSettings } from '../../codewhisperer/util/codewhispererSettings'
1918
import { activate as activateLsp } from './lspClient'
2019
import { telemetry } from '../../shared/telemetry'
2120
import { isCloud9 } from '../../shared/extensionUtilities'
2221
import { fs, globals, ToolkitError } from '../../shared'
23-
import { AuthUtil } from '../../codewhisperer'
2422
import { isWeb } from '../../shared/extensionGlobals'
2523
import { getUserAgent } from '../../shared/telemetry/util'
2624
import { isAmazonInternalOs } from '../../shared/vscode/env'
@@ -68,9 +66,16 @@ export interface Manifest {
6866
}
6967
const manifestUrl = 'https://aws-toolkit-language-servers.amazonaws.com/q-context/manifest.json'
7068
// this LSP client in Q extension is only going to work with these LSP server versions
71-
const supportedLspServerVersions = ['0.1.13']
69+
const supportedLspServerVersions = ['0.1.22', '0.1.19']
7270

7371
const nodeBinName = process.platform === 'win32' ? 'node.exe' : 'node'
72+
73+
export interface BuildIndexConfig {
74+
startUrl?: string
75+
maxIndexSize: number
76+
isVectorIndexEnabled: boolean
77+
}
78+
7479
/*
7580
* LSP Controller manages the status of Amazon Q LSP:
7681
* 1. Downloading, verifying and installing LSP using DEXP LSP manifest and CDN.
@@ -281,7 +286,7 @@ export class LspController {
281286
}
282287

283288
async query(s: string): Promise<RelevantTextDocument[]> {
284-
const chunks: Chunk[] | undefined = await LspClient.instance.query(s)
289+
const chunks: Chunk[] | undefined = await LspClient.instance.queryVectorIndex(s)
285290
const resp: RelevantTextDocument[] = []
286291
chunks?.forEach((chunk) => {
287292
const text = chunk.context ? chunk.context : chunk.content
@@ -303,7 +308,15 @@ export class LspController {
303308
return resp
304309
}
305310

306-
async buildIndex() {
311+
async queryInlineProjectContext(query: string, path: string) {
312+
try {
313+
return await LspClient.instance.queryInlineProjectContext(query, path)
314+
} catch (e) {
315+
return []
316+
}
317+
}
318+
319+
async buildIndex(buildIndexConfig: BuildIndexConfig) {
307320
getLogger().info(`LspController: Starting to build index of project`)
308321
const start = performance.now()
309322
const projPaths = getProjectPaths()
@@ -318,18 +331,16 @@ export class LspController {
318331
projPaths,
319332
vscode.workspace.workspaceFolders as CurrentWsFolders,
320333
true,
321-
CodeWhispererSettings.instance.getMaxIndexSize() * 1024 * 1024
334+
buildIndexConfig.maxIndexSize * 1024 * 1024
322335
)
323336
const totalSizeBytes = files.reduce(
324337
(accumulator, currentFile) => accumulator + currentFile.fileSizeBytes,
325338
0
326339
)
327340
getLogger().info(`LspController: Found ${files.length} files in current project ${getProjectPaths()}`)
328-
const resp = await LspClient.instance.indexFiles(
329-
files.map((f) => f.fileUri.fsPath),
330-
projRoot,
331-
false
332-
)
341+
const config = buildIndexConfig.isVectorIndexEnabled ? 'all' : 'default'
342+
const r = files.map((f) => f.fileUri.fsPath)
343+
const resp = await LspClient.instance.buildIndex(r, projRoot, config)
333344
if (resp) {
334345
getLogger().debug(`LspController: Finish building index of project`)
335346
const usage = await LspClient.instance.getLspServerUsage()
@@ -340,31 +351,36 @@ export class LspController {
340351
amazonqIndexMemoryUsageInMB: usage ? usage.memoryUsage / (1024 * 1024) : undefined,
341352
amazonqIndexCpuUsagePercentage: usage ? usage.cpuUsage : undefined,
342353
amazonqIndexFileSizeInMB: totalSizeBytes / (1024 * 1024),
343-
credentialStartUrl: AuthUtil.instance.startUrl,
354+
credentialStartUrl: buildIndexConfig.startUrl,
344355
})
345356
} else {
346-
getLogger().error(`LspController: Failed to build index of project`)
347-
telemetry.amazonq_indexWorkspace.emit({
348-
duration: performance.now() - start,
349-
result: 'Failed',
350-
amazonqIndexFileCount: 0,
351-
amazonqIndexFileSizeInMB: 0,
352-
})
357+
// TODO: Re-enable this code path for LSP 0.1.20+
358+
// getLogger().error(`LspController: Failed to build index of project`)
359+
// telemetry.amazonq_indexWorkspace.emit({
360+
// duration: performance.now() - start,
361+
// result: 'Failed',
362+
// amazonqIndexFileCount: 0,
363+
// amazonqIndexFileSizeInMB: 0,
364+
// reason: `Unknown`,
365+
// })
353366
}
354-
} catch (e) {
367+
} catch (error) {
368+
//TODO: use telemetry.run()
355369
getLogger().error(`LspController: Failed to build index of project`)
356370
telemetry.amazonq_indexWorkspace.emit({
357371
duration: performance.now() - start,
358372
result: 'Failed',
359373
amazonqIndexFileCount: 0,
360374
amazonqIndexFileSizeInMB: 0,
375+
reason: `${error instanceof Error ? error.name : 'Unknown'}`,
376+
reasonDesc: `Error when building index. ${error instanceof Error ? error.message : error}`,
361377
})
362378
} finally {
363379
this._isIndexingInProgress = false
364380
}
365381
}
366382

367-
async trySetupLsp(context: vscode.ExtensionContext) {
383+
async trySetupLsp(context: vscode.ExtensionContext, buildIndexConfig: BuildIndexConfig) {
368384
if (isCloud9() || isWeb() || isAmazonInternalOs()) {
369385
getLogger().warn('LspController: Skipping LSP setup. LSP is not compatible with the current environment. ')
370386
// do not do anything if in Cloud9 or Web mode or in AL2 (AL2 does not support node v18+)
@@ -378,9 +394,7 @@ export class LspController {
378394
try {
379395
await activateLsp(context)
380396
getLogger().info('LspController: LSP activated')
381-
if (CodeWhispererSettings.instance.isLocalIndexEnabled()) {
382-
void LspController.instance.buildIndex()
383-
}
397+
void LspController.instance.buildIndex(buildIndexConfig)
384398
// log the LSP server CPU and Memory usage per 30 minutes.
385399
globals.clock.setInterval(
386400
async () => {

packages/core/src/amazonq/lsp/types.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ export type IndexRequestPayload = {
1111
refresh: boolean
1212
}
1313

14-
export type IndexRequest = string
15-
16-
export const IndexRequestType: RequestType<IndexRequest, any, any> = new RequestType('lsp/index')
17-
1814
export type ClearRequest = string
1915

2016
export const ClearRequestType: RequestType<ClearRequest, any, any> = new RequestType('lsp/clear')
@@ -23,10 +19,6 @@ export type QueryRequest = string
2319

2420
export const QueryRequestType: RequestType<QueryRequest, any, any> = new RequestType('lsp/query')
2521

26-
export type UpdateIndexRequest = string
27-
28-
export const UpdateIndexRequestType: RequestType<UpdateIndexRequest, any, any> = new RequestType('lsp/updateIndex')
29-
3022
export type GetUsageRequest = string
3123

3224
export const GetUsageRequestType: RequestType<GetUsageRequest, any, any> = new RequestType('lsp/getUsage')
@@ -35,3 +27,40 @@ export interface Usage {
3527
memoryUsage: number
3628
cpuUsage: number
3729
}
30+
31+
export type BuildIndexRequestPayload = {
32+
filePaths: string[]
33+
projectRoot: string
34+
config: string
35+
language: string
36+
}
37+
38+
export type BuildIndexRequest = string
39+
40+
export const BuildIndexRequestType: RequestType<BuildIndexRequest, any, any> = new RequestType('lsp/buildIndex')
41+
42+
export type UpdateIndexV2Request = string
43+
44+
export type UpdateIndexV2RequestPayload = { filePaths: string[]; updateMode: string }
45+
46+
export const UpdateIndexV2RequestType: RequestType<UpdateIndexV2Request, any, any> = new RequestType(
47+
'lsp/updateIndexV2'
48+
)
49+
50+
export type QueryInlineProjectContextRequest = string
51+
export type QueryInlineProjectContextRequestPayload = {
52+
query: string
53+
filePath: string
54+
}
55+
export const QueryInlineProjectContextRequestType: RequestType<QueryInlineProjectContextRequest, any, any> =
56+
new RequestType('lsp/queryInlineProjectContext')
57+
58+
export type QueryVectorIndexRequestPayload = { query: string }
59+
60+
export type QueryVectorIndexRequest = string
61+
62+
export const QueryVectorIndexRequestType: RequestType<QueryVectorIndexRequest, any, any> = new RequestType(
63+
'lsp/queryVectorIndex'
64+
)
65+
66+
export type IndexConfig = 'all' | 'default'

packages/core/src/codewhisperer/models/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export type UtgStrategy = 'ByName' | 'ByContent'
4848

4949
export type CrossFileStrategy = 'OpenTabs_BM25'
5050

51-
export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty'
51+
export type SupplementalContextStrategy = CrossFileStrategy | UtgStrategy | 'Empty' | 'LSP'
5252

5353
export interface CodeWhispererSupplementalContext {
5454
isUtg: boolean

0 commit comments

Comments
 (0)