Skip to content

Commit 013c54f

Browse files
feat(amazonq): LSP -- Implement Initialize message (#5367)
Initialize message is the first request sent from the client and sets up the LSP session configuration for subsequent communications. Includes: Process ID for server management Client capabilities for LSP features Client information (IDE/product details) Workspace folders for project context Extended client metadata specific to Amazon Q ***needs tests, not currently working
1 parent ffbab15 commit 013c54f

File tree

2 files changed

+115
-13
lines changed

2 files changed

+115
-13
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,36 @@ import com.intellij.openapi.project.Project
1818
import com.intellij.openapi.util.Key
1919
import com.intellij.util.io.await
2020
import kotlinx.coroutines.CoroutineScope
21+
import kotlinx.coroutines.TimeoutCancellationException
2122
import kotlinx.coroutines.launch
23+
import kotlinx.coroutines.time.withTimeout
24+
import org.eclipse.lsp4j.ClientCapabilities
25+
import org.eclipse.lsp4j.ClientInfo
26+
import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities
2227
import org.eclipse.lsp4j.InitializeParams
2328
import org.eclipse.lsp4j.InitializedParams
29+
import org.eclipse.lsp4j.SynchronizationCapabilities
30+
import org.eclipse.lsp4j.TextDocumentClientCapabilities
31+
import org.eclipse.lsp4j.WorkspaceClientCapabilities
32+
import org.eclipse.lsp4j.WorkspaceFolder
2433
import org.eclipse.lsp4j.jsonrpc.Launcher
2534
import org.eclipse.lsp4j.launch.LSPLauncher
2635
import org.slf4j.event.Level
2736
import software.aws.toolkits.core.utils.getLogger
2837
import software.aws.toolkits.core.utils.warn
2938
import software.aws.toolkits.jetbrains.isDeveloperMode
39+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata
40+
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
3041
import java.io.IOException
3142
import java.io.OutputStreamWriter
3243
import java.io.PipedInputStream
3344
import java.io.PipedOutputStream
3445
import java.io.PrintWriter
3546
import java.io.StringWriter
47+
import java.net.URI
3648
import java.nio.charset.StandardCharsets
49+
import java.time.Duration
3750
import java.util.concurrent.Future
38-
3951
// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
4052
// JB impl and redhat both use a wrapper to handle input buffering issue
4153
internal class LSPProcessListener : ProcessListener {
@@ -70,7 +82,7 @@ internal class LSPProcessListener : ProcessListener {
7082
}
7183

7284
@Service(Service.Level.PROJECT)
73-
class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disposable {
85+
class AmazonQLspService(private val project: Project, private val cs: CoroutineScope) : Disposable {
7486
private val launcher: Launcher<AmazonQLanguageServer>
7587

7688
private val languageServer: AmazonQLanguageServer
@@ -80,6 +92,57 @@ class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disp
8092
private val launcherFuture: Future<Void>
8193
private val launcherHandler: KillableProcessHandler
8294

95+
private fun createClientCapabilities(): ClientCapabilities =
96+
ClientCapabilities().apply {
97+
textDocument = TextDocumentClientCapabilities().apply {
98+
// For didSaveTextDocument, other textDocument/ messages always mandatory
99+
synchronization = SynchronizationCapabilities().apply {
100+
didSave = true
101+
}
102+
}
103+
104+
workspace = WorkspaceClientCapabilities().apply {
105+
applyEdit = false
106+
107+
// For workspace folder changes
108+
workspaceFolders = true
109+
110+
// For file operations (create, delete)
111+
fileOperations = FileOperationsWorkspaceCapabilities().apply {
112+
didCreate = true
113+
didDelete = true
114+
}
115+
}
116+
}
117+
118+
// needs case handling when project's base path is null: default projects/unit tests
119+
private fun createWorkspaceFolders(): List<WorkspaceFolder> =
120+
project.basePath?.let { basePath ->
121+
listOf(
122+
WorkspaceFolder(
123+
URI("file://$basePath").toString(),
124+
project.name
125+
)
126+
)
127+
}.orEmpty() // no folders to report or workspace not folder based
128+
129+
private fun createClientInfo(): ClientInfo {
130+
val metadata = ClientMetadata.getDefault()
131+
return ClientInfo().apply {
132+
name = metadata.awsProduct.toString()
133+
version = metadata.awsVersion
134+
}
135+
}
136+
137+
private fun createInitializeParams(): InitializeParams =
138+
InitializeParams().apply {
139+
processId = ProcessHandle.current().pid().toInt()
140+
capabilities = createClientCapabilities()
141+
clientInfo = createClientInfo()
142+
workspaceFolders = createWorkspaceFolders()
143+
initializationOptions = createExtendedClientMetadata()
144+
}
145+
83146
init {
84147
val cmd = GeneralCommandLine("amazon-q-lsp")
85148

@@ -116,18 +179,14 @@ class AmazonQLspService(project: Project, private val cs: CoroutineScope) : Disp
116179
launcherFuture = launcher.startListening()
117180

118181
cs.launch {
119-
val initializeResult = languageServer.initialize(
120-
InitializeParams().apply {
121-
// does this work on windows
122-
processId = ProcessHandle.current().pid().toInt()
123-
// capabilities
124-
// client info
125-
// trace?
126-
// workspace folders?
127-
// anything else we need?
182+
val initializeResult = try {
183+
withTimeout(Duration.ofSeconds(30)) {
184+
languageServer.initialize(createInitializeParams()).await()
128185
}
129-
// probably need a timeout
130-
).await()
186+
} catch (e: TimeoutCancellationException) {
187+
LOG.warn { "LSP initialization timed out" }
188+
null
189+
}
131190

132191
// then if this succeeds then we can allow the client to send requests
133192
if (initializeResult == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.model
5+
6+
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
7+
8+
data class ExtendedClientMetadata(
9+
val aws: AwsMetadata,
10+
)
11+
12+
data class AwsMetadata(
13+
val clientInfo: ClientInfoMetadata,
14+
)
15+
16+
data class ClientInfoMetadata(
17+
val extension: ExtensionMetadata,
18+
val clientId: String,
19+
val version: String,
20+
val name: String,
21+
)
22+
23+
data class ExtensionMetadata(
24+
val name: String,
25+
val version: String,
26+
)
27+
28+
fun createExtendedClientMetadata(): ExtendedClientMetadata {
29+
val metadata = ClientMetadata.getDefault()
30+
return ExtendedClientMetadata(
31+
aws = AwsMetadata(
32+
clientInfo = ClientInfoMetadata(
33+
extension = ExtensionMetadata(
34+
name = metadata.awsProduct.toString(),
35+
version = metadata.awsVersion
36+
),
37+
clientId = metadata.clientId,
38+
version = metadata.parentProductVersion,
39+
name = metadata.parentProduct
40+
)
41+
)
42+
)
43+
}

0 commit comments

Comments
 (0)