Skip to content

Commit c4aa8cb

Browse files
committed
Added Manifest Fetcher
1 parent 5039aad commit c4aa8cb

File tree

2 files changed

+147
-1
lines changed

2 files changed

+147
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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.artifacts
5+
6+
import com.intellij.openapi.util.text.StringUtil
7+
import com.intellij.util.io.DigestUtil
8+
import com.intellij.util.io.HttpRequests
9+
import software.amazon.awssdk.utils.UserHomeDirectoryUtils
10+
import software.aws.toolkits.core.utils.exists
11+
import software.aws.toolkits.core.utils.getLogger
12+
import software.aws.toolkits.core.utils.readText
13+
import software.aws.toolkits.jetbrains.core.getTextFromUrl
14+
import software.aws.toolkits.jetbrains.core.saveFileFromUrl
15+
import software.aws.toolkits.jetbrains.services.amazonq.project.manifest.ManifestManager
16+
import java.nio.file.Path
17+
import java.nio.file.Paths
18+
19+
20+
class ManifestFetcher {
21+
22+
private val lspManifestUrl = "https://aws-toolkit-language-servers.amazonaws.com/codewhisperer/0/manifest.json"
23+
private val manifestManager = ManifestManager()
24+
private val lspManifestFilePath: Path = Paths.get(UserHomeDirectoryUtils.userHomeDirectory(), ".aws", "amazonq", "cache", "lspManifest.js")
25+
26+
companion object {
27+
private val logger = getLogger<ManifestFetcher>()
28+
}
29+
30+
/**
31+
* Method which will be used to fetch latest manifest.
32+
* */
33+
fun fetch() : ManifestManager.Manifest? {
34+
val localManifest = fetchManifestFromLocal()
35+
if (localManifest != null) {
36+
return localManifest
37+
}
38+
val remoteManifest = fetchManifestFromRemote() ?: return null
39+
return remoteManifest
40+
}
41+
42+
private fun fetchManifestFromRemote() : ManifestManager.Manifest? {
43+
val manifest : ManifestManager.Manifest?
44+
try {
45+
val manifestString = getTextFromUrl(lspManifestUrl)
46+
manifest = manifestManager.readManifestFile(manifestString) ?: return null
47+
}
48+
catch (e: Exception) {
49+
logger.error("error fetching lsp manifest from remote URL ${e.message}", e)
50+
return null
51+
}
52+
if (manifest.isManifestDeprecated == true) {
53+
logger.info("Manifest is deprecated")
54+
return null
55+
}
56+
updateManifestCache()
57+
logger.info("Using manifest found from remote URL")
58+
return manifest
59+
}
60+
61+
private fun updateManifestCache() {
62+
try {
63+
saveFileFromUrl(lspManifestUrl, lspManifestFilePath)
64+
}
65+
catch (e: Exception) {
66+
logger.error("error occurred while saving lsp manifest to local cache ${e.message}", e)
67+
}
68+
}
69+
70+
private fun fetchManifestFromLocal() : ManifestManager.Manifest? {
71+
var manifest : ManifestManager.Manifest? = null
72+
val localETag = getManifestETagFromLocal()
73+
val remoteETag = getManifestETagFromUrl()
74+
// If local and remote have same ETag, we can re-use the manifest file from local to fetch artifacts.
75+
if (localETag != null && remoteETag != null && localETag == remoteETag) {
76+
try {
77+
val manifestContent = lspManifestFilePath.readText()
78+
manifest = manifestManager.readManifestFile(manifestContent) ?: return null
79+
}
80+
catch (e: Exception) {
81+
logger.error("error reading lsp manifest file from local ${e.message}", e)
82+
return null
83+
}
84+
}
85+
logger.info("Re-using lsp manifest from local.")
86+
return manifest
87+
}
88+
89+
private fun getManifestETagFromLocal() : String? {
90+
if(lspManifestFilePath.exists()) {
91+
val messageDigest = DigestUtil.md5()
92+
DigestUtil.updateContentHash(messageDigest, lspManifestFilePath)
93+
return StringUtil.toHexString(messageDigest.digest())
94+
}
95+
return null
96+
}
97+
98+
private fun getManifestETagFromUrl() : String? {
99+
try {
100+
val actualETag = HttpRequests.head(lspManifestUrl)
101+
102+
.userAgent("AWS Toolkit for JetBrains")
103+
.connect { request ->
104+
request.connection.headerFields["ETag"]?.firstOrNull().orEmpty()
105+
}
106+
return actualETag.trim('"')
107+
}
108+
catch (e: Exception) {
109+
logger.error("error fetching ETag of lsp manifest from url.", e)
110+
}
111+
return null
112+
}
113+
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/project/manifest/ManifestManager.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,45 @@ class ManifestManager {
4141
@JsonProperty("contents")
4242
val contents: List<TargetContent>? = emptyList(),
4343
)
44+
45+
data class RunTime(
46+
@JsonIgnoreProperties(ignoreUnknown = true)
47+
@JsonProperty("name")
48+
val name: String? = null,
49+
@JsonProperty("version")
50+
val version: String? = null
51+
)
52+
53+
data class Capabilities(
54+
@JsonIgnoreProperties(ignoreUnknown = true)
55+
@JsonProperty("name")
56+
val name: String? = null,
57+
@JsonProperty("version")
58+
val version: String? = null
59+
)
60+
61+
data class Protocol(
62+
@JsonIgnoreProperties(ignoreUnknown = true)
63+
@JsonProperty("name")
64+
val name: String?= null,
65+
@JsonProperty("version")
66+
val version: String? = null
67+
)
68+
4469
data class Version(
4570
@JsonIgnoreProperties(ignoreUnknown = true)
4671
@JsonProperty("serverVersion")
4772
val serverVersion: String? = null,
4873
@JsonProperty("isDelisted")
4974
val isDelisted: Boolean? = null,
75+
@JsonProperty("runtime")
76+
val runtime: RunTime? = null,
77+
@JsonProperty("capabilities")
78+
val capabilities: List<Capabilities>? = emptyList(),
79+
@JsonProperty("protocol")
80+
val protocol: List<Protocol>? = emptyList(),
81+
@JsonProperty("thirdPartyLicenses")
82+
val thirdPartyLicenses: String? = null,
5083
@JsonProperty("targets")
5184
val targets: List<VersionTarget>? = emptyList(),
5285
)
@@ -67,7 +100,7 @@ class ManifestManager {
67100

68101
fun getManifest(): Manifest? = fetchFromRemoteAndSave()
69102

70-
private fun readManifestFile(content: String): Manifest? {
103+
fun readManifestFile(content: String): Manifest? {
71104
try {
72105
return mapper.readValue<Manifest>(content)
73106
} catch (e: Exception) {

0 commit comments

Comments
 (0)