Skip to content

Commit 4d6f886

Browse files
committed
fix(lsp): install failure does not give enough info
Problem: When lsp fetch/install/start fails it does not mention the download path, which could help with troubleshooting. #6972 [info] using amazonqWorkspaceLsp service configuration: default [info] lsp: Failed to download latest "AmazonQ-Workspace" manifest. Falling back to local manifest. [info] lsp: Finished setting up LSP server [info] [Error] Starting client failed [info] Error: write EPIPE Solution: - Validate that `node` can actually run, before passing it to `LspClient`. - Add more logging.
1 parent dc43ad2 commit 4d6f886

File tree

6 files changed

+102
-29
lines changed

6 files changed

+102
-29
lines changed

packages/amazonq/src/lsp/chat/webviewProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class AmazonQChatViewProvider implements WebviewViewProvider {
5050
) {
5151
this.webview = webviewView.webview
5252

53-
const lspDir = Uri.parse(LanguageServerResolver.defaultDir)
53+
const lspDir = Uri.parse(LanguageServerResolver.defaultDir())
5454
webviewView.webview.options = {
5555
enableScripts: true,
5656
enableCommandUris: true,

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

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ import globals from '../../shared/extensionGlobals'
4141
import { ResourcePaths } from '../../shared/lsp/types'
4242
import { createServerOptions } from '../../shared/lsp/utils/platform'
4343
import { waitUntil } from '../../shared/utilities/timeoutUtils'
44+
import { tryRun } from '../../shared/utilities/pathFind'
45+
import { ToolkitError } from '../../shared/errors'
4446

4547
const localize = nls.loadMessageBundle()
4648

4749
const key = crypto.randomBytes(32)
50+
const logger = getLogger('amazonqLsp')
4851

4952
/**
5053
* LspClient manages the API call between VS Code extension and LSP server
@@ -80,7 +83,7 @@ export class LspClient {
8083
const resp = await this.client?.sendRequest(BuildIndexRequestType, encryptedRequest)
8184
return resp
8285
} catch (e) {
83-
getLogger().error(`LspClient: buildIndex error: ${e}`)
86+
logger.error(`LspClient: buildIndex error: ${e}`)
8487
return undefined
8588
}
8689
}
@@ -95,7 +98,7 @@ export class LspClient {
9598
const resp = await this.client?.sendRequest(QueryVectorIndexRequestType, encryptedRequest)
9699
return resp
97100
} catch (e) {
98-
getLogger().error(`LspClient: queryVectorIndex error: ${e}`)
101+
logger.error(`LspClient: queryVectorIndex error: ${e}`)
99102
return []
100103
}
101104
}
@@ -111,7 +114,7 @@ export class LspClient {
111114
const resp: any = await this.client?.sendRequest(QueryInlineProjectContextRequestType, encrypted)
112115
return resp
113116
} catch (e) {
114-
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
117+
logger.error(`LspClient: queryInlineProjectContext error: ${e}`)
115118
throw e
116119
}
117120
}
@@ -132,7 +135,7 @@ export class LspClient {
132135
const resp = await this.client?.sendRequest(UpdateIndexV2RequestType, encryptedRequest)
133136
return resp
134137
} catch (e) {
135-
getLogger().error(`LspClient: updateIndex error: ${e}`)
138+
logger.error(`LspClient: updateIndex error: ${e}`)
136139
return undefined
137140
}
138141
}
@@ -144,7 +147,7 @@ export class LspClient {
144147
const resp: any = await this.client?.sendRequest(QueryRepomapIndexRequestType, await this.encrypt(request))
145148
return resp
146149
} catch (e) {
147-
getLogger().error(`LspClient: QueryRepomapIndex error: ${e}`)
150+
logger.error(`LspClient: QueryRepomapIndex error: ${e}`)
148151
throw e
149152
}
150153
}
@@ -157,7 +160,7 @@ export class LspClient {
157160
)
158161
return resp
159162
} catch (e) {
160-
getLogger().error(`LspClient: queryInlineProjectContext error: ${e}`)
163+
logger.error(`LspClient: queryInlineProjectContext error: ${e}`)
161164
throw e
162165
}
163166
}
@@ -174,7 +177,7 @@ export class LspClient {
174177
)
175178
return resp
176179
} catch (e) {
177-
getLogger().error(`LspClient: getContextCommandItems error: ${e}`)
180+
logger.error(`LspClient: getContextCommandItems error: ${e}`)
178181
throw e
179182
}
180183
}
@@ -190,7 +193,7 @@ export class LspClient {
190193
)
191194
return resp || []
192195
} catch (e) {
193-
getLogger().error(`LspClient: getContextCommandPrompt error: ${e}`)
196+
logger.error(`LspClient: getContextCommandPrompt error: ${e}`)
194197
throw e
195198
}
196199
}
@@ -204,7 +207,7 @@ export class LspClient {
204207
)
205208
return resp
206209
} catch (e) {
207-
getLogger().error(`LspClient: getIndexSequenceNumber error: ${e}`)
210+
logger.error(`LspClient: getIndexSequenceNumber error: ${e}`)
208211
throw e
209212
}
210213
}
@@ -223,14 +226,18 @@ export class LspClient {
223226
}
224227
}
225228
/**
226-
* Activates the language server, this will start LSP server running over IPC protocol.
227-
* It will create a output channel named Amazon Q Language Server.
228-
* This function assumes the LSP server has already been downloaded.
229+
* Activates the language server (assumes the LSP server has already been downloaded):
230+
* 1. start LSP server running over IPC protocol.
231+
* 2. create a output channel named Amazon Q Language Server.
229232
*/
230233
export async function activate(extensionContext: ExtensionContext, resourcePaths: ResourcePaths) {
231-
LspClient.instance
234+
LspClient.instance // Tickle the singleton... :/
232235
const toDispose = extensionContext.subscriptions
233236

237+
if (await tryRun(resourcePaths.node, ['--version'])) {
238+
throw new ToolkitError(`failed to run node: "${resourcePaths.node}"`)
239+
}
240+
234241
let rangeFormatting: Disposable | undefined
235242
// The debug options for the server
236243
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
@@ -259,6 +266,7 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
259266
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
260267
}
261268

269+
// TODO(jmkeyes): this overwrites the above...?
262270
serverOptions = createServerOptions({
263271
encryptionKey: key,
264272
executable: resourcePaths.node,
@@ -359,10 +367,15 @@ export async function activate(extensionContext: ExtensionContext, resourcePaths
359367
})
360368
)
361369

362-
return LspClient.instance.client.onReady().then(() => {
363-
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
364-
toDispose.push(disposableFunc)
365-
})
370+
return LspClient.instance.client.onReady().then(
371+
() => {
372+
const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void }
373+
toDispose.push(disposableFunc)
374+
},
375+
(reason) => {
376+
logger.error('LspClient.instance.client.onReady() failed: %O', reason)
377+
}
378+
)
366379
}
367380

368381
export async function deactivate(): Promise<any> {

packages/core/src/amazonq/webview/generators/webViewContent.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { AuthUtil } from '../../../codewhisperer/util/authUtil'
99
import { FeatureConfigProvider, FeatureContext } from '../../../shared/featureConfig'
1010
import globals from '../../../shared/extensionGlobals'
1111
import { isSageMaker } from '../../../shared/extensionUtilities'
12-
import { RegionProfile } from '../../../codewhisperer/models/model'
1312
import { AmazonQPromptSettings } from '../../../shared/settings'
1413

1514
export class WebViewContentGenerator {
@@ -90,10 +89,10 @@ export class WebViewContentGenerator {
9089
// only show profile card when the two conditions
9190
// 1. profile count >= 2
9291
// 2. not default (fallback) which has empty arn
93-
let regionProfile: RegionProfile | undefined = AuthUtil.instance.regionProfileManager.activeRegionProfile
94-
if (AuthUtil.instance.regionProfileManager.profiles.length === 1) {
95-
regionProfile = undefined
96-
}
92+
const regionProfile =
93+
AuthUtil.instance.regionProfileManager.profiles.length === 1
94+
? undefined
95+
: AuthUtil.instance.regionProfileManager.activeRegionProfile
9796

9897
const regionProfileString: string = JSON.stringify(regionProfile)
9998
const authState = (await AuthUtil.instance.getChatAuthState()).amazonQ
@@ -104,7 +103,7 @@ export class WebViewContentGenerator {
104103
<script type="text/javascript">
105104
const init = () => {
106105
createMynahUI(
107-
acquireVsCodeApi(),
106+
acquireVsCodeApi(),
108107
${authState === 'connected'},
109108
${featureConfigsString},
110109
${welcomeLoadCount},

packages/core/src/shared/lsp/baseLspInstaller.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,19 @@ export abstract class BaseLspInstaller<T extends ResourcePaths = ResourcePaths,
5454
const deletedVersions = await cleanLspDownloads(manifest.versions, nodePath.dirname(assetDirectory))
5555
this.logger.debug(`cleaning old LSP versions deleted ${deletedVersions.length} versions`)
5656

57-
return {
57+
const r = {
5858
...installationResult,
59+
// Example:
60+
// ```
61+
// resourcePaths = {
62+
// lsp = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/aws-lsp-codewhisperer.js'
63+
// node = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/node'
64+
// ui = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/clients/amazonq-ui.js'
65+
// }
66+
// ```
5967
resourcePaths: this.resourcePaths(assetDirectory),
6068
}
69+
return r
6170
}
6271

6372
protected abstract postInstall(assetDirectory: string): Promise<void>

packages/core/src/shared/lsp/lspResolver.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,40 @@ export class LanguageServerResolver {
4747

4848
const serverResolvers: StageResolver<LspResult>[] = [
4949
{
50+
// 1: Use the current local ("cached") LSP server bundle, if any.
5051
resolve: async () => await this.getLocalServer(cacheDirectory, latestVersion, targetContents),
5152
telemetryMetadata: { id: this.lsName, languageServerLocation: 'cache' },
5253
},
5354
{
55+
// 2: Download the latest LSP server bundle.
5456
resolve: async () => await this.fetchRemoteServer(cacheDirectory, latestVersion, targetContents),
5557
telemetryMetadata: { id: this.lsName, languageServerLocation: 'remote' },
5658
},
5759
{
60+
// 3: If the download fails, try an older, cached version.
5861
resolve: async () => await this.getFallbackServer(latestVersion),
5962
telemetryMetadata: { id: this.lsName, languageServerLocation: 'fallback' },
6063
},
6164
]
6265

63-
return await tryStageResolvers('getServer', serverResolvers, getServerVersion)
66+
/**
67+
* Example:
68+
* ```
69+
* LspResult {
70+
* assetDirectory = "<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0"
71+
* location = 'cache'
72+
* version = '3.3.0'
73+
* }
74+
* ```
75+
*/
76+
const resolved = await tryStageResolvers('getServer', serverResolvers, getServerVersion)
77+
return resolved
6478
} finally {
6579
logger.info(`Finished setting up LSP server`)
6680
}
6781
}
6882

83+
/** Finds an older, cached version of the LSP server bundle. */
6984
private async getFallbackServer(latestVersion: LspVersion): Promise<LspResult> {
7085
const fallbackDirectory = await this.getFallbackDir(latestVersion.serverVersion)
7186
if (!fallbackDirectory) {
@@ -96,6 +111,7 @@ export class LanguageServerResolver {
96111
return timeout
97112
}
98113

114+
/** Downloads the latest LSP server bundle. */
99115
private async fetchRemoteServer(
100116
cacheDirectory: string,
101117
latestVersion: LspVersion,
@@ -117,6 +133,7 @@ export class LanguageServerResolver {
117133
}
118134
}
119135

136+
/** Gets the current local ("cached") LSP server bundle. */
120137
private async getLocalServer(
121138
cacheDirectory: string,
122139
latestVersion: LspVersion,
@@ -417,13 +434,17 @@ export class LanguageServerResolver {
417434
return version.targets.find((x) => x.arch === arch && x.platform === platform)
418435
}
419436

420-
// lazy calls to `getApplicationSupportFolder()` to avoid failure on windows.
421-
public static get defaultDir() {
437+
/**
438+
* Gets platform-specific "cache" dir ("$LOCALAPPDATA/aws/…" or "~/.cache/aws/…").
439+
*
440+
* Lazy-calls `getCacheDir()` to avoid failure on Windows.
441+
*/
442+
public static defaultDir() {
422443
return path.join(fs.getCacheDir(), `aws/toolkits/language-servers`)
423444
}
424445

425446
defaultDownloadFolder() {
426-
return path.join(LanguageServerResolver.defaultDir, `${this.lsName}`)
447+
return path.join(LanguageServerResolver.defaultDir(), `${this.lsName}`)
427448
}
428449

429450
private getDownloadDirectory(version: string) {

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,48 @@ import { LanguageServerLocation, ManifestLocation } from '../telemetry/telemetry
99
export const logger = getLogger('lsp')
1010

1111
export interface LspResult {
12+
/** Example: `"cache"` */
1213
location: LanguageServerLocation
14+
/** Example: `"3.3.0"` */
1315
version: string
16+
/** Example: `"<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0"` */
1417
assetDirectory: string
1518
}
1619

20+
/**
21+
* Example:
22+
* ```
23+
* resourcePaths = {
24+
* lsp = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/aws-lsp-codewhisperer.js'
25+
* node = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/node'
26+
* ui = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/clients/amazonq-ui.js'
27+
* }
28+
* ```
29+
*/
1730
export interface ResourcePaths {
31+
/**
32+
* Path to `.js` bundle to be executed by `node`.
33+
* Example: `"<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/aws-lsp-codewhisperer.js"`
34+
*/
1835
lsp: string
36+
/**
37+
* Path to `node` (or `node.exe`) executable/binary.
38+
* Example: `"<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/node"`
39+
*/
1940
node: string
2041
}
2142

2243
export interface LspResolution<T extends ResourcePaths> extends LspResult {
44+
/**
45+
* Example:
46+
* ```
47+
* resourcePaths = {
48+
* lsp = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/aws-lsp-codewhisperer.js'
49+
* node = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/servers/node'
50+
* ui = '<cachedir>/aws/toolkits/language-servers/AmazonQ/3.3.0/clients/amazonq-ui.js'
51+
* }
52+
* ```
53+
*/
2354
resourcePaths: T
2455
}
2556

0 commit comments

Comments
 (0)