@aws/lsp-codewhisperer 0.0.35 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/out/client/token/bearer-token-service.json +91 -225
- package/out/language-server/agenticChat/agenticChatController.js +7 -0
- package/out/language-server/agenticChat/agenticChatController.js.map +1 -1
- package/out/language-server/agenticChat/context/agenticChatTriggerContext.js +1 -1
- package/out/language-server/agenticChat/context/agenticChatTriggerContext.js.map +1 -1
- package/out/language-server/agenticChat/tools/listDirectory.js +5 -5
- package/out/language-server/agenticChat/tools/listDirectory.js.map +1 -1
- package/out/language-server/chat/chatController.js +1 -1
- package/out/language-server/chat/chatController.js.map +1 -1
- package/out/language-server/chat/chatSessionService.d.ts +5 -0
- package/out/language-server/chat/chatSessionService.js +4 -1
- package/out/language-server/chat/chatSessionService.js.map +1 -1
- package/out/language-server/chat/constants.d.ts +2 -0
- package/out/language-server/chat/constants.js +14 -1
- package/out/language-server/chat/constants.js.map +1 -1
- package/out/language-server/chat/contexts/triggerContext.d.ts +3 -1
- package/out/language-server/chat/contexts/triggerContext.js +14 -1
- package/out/language-server/chat/contexts/triggerContext.js.map +1 -1
- package/out/language-server/configuration/qConfigurationServer.d.ts +8 -1
- package/out/language-server/configuration/qConfigurationServer.js +107 -8
- package/out/language-server/configuration/qConfigurationServer.js.map +1 -1
- package/out/language-server/inline-completion/codeWhispererServer.js +15 -2
- package/out/language-server/inline-completion/codeWhispererServer.js.map +1 -1
- package/out/language-server/netTransform/resources/SupportedProjects.js +1 -7
- package/out/language-server/netTransform/resources/SupportedProjects.js.map +1 -1
- package/out/language-server/workspaceContext/artifactManager.d.ts +44 -0
- package/out/language-server/workspaceContext/artifactManager.js +496 -0
- package/out/language-server/workspaceContext/artifactManager.js.map +1 -0
- package/out/language-server/workspaceContext/client.d.ts +23 -0
- package/out/language-server/workspaceContext/client.js +172 -0
- package/out/language-server/workspaceContext/client.js.map +1 -0
- package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.d.ts +15 -0
- package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js +129 -0
- package/out/language-server/workspaceContext/dependency/dependencyDiscoverer.js.map +1 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.d.ts +18 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.js +204 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JSTSDependencyHandler.js.map +1 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.d.ts +16 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.js +160 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/JavaDependencyHandler.js.map +1 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.d.ts +78 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js +255 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandler.js.map +1 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandlerFactory.d.ts +7 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandlerFactory.js +23 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/LanguageDependencyHandlerFactory.js.map +1 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.d.ts +46 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.js +233 -0
- package/out/language-server/workspaceContext/dependency/dependencyHandler/PythonDependencyHandler.js.map +1 -0
- package/out/language-server/workspaceContext/javaManager.d.ts +92 -0
- package/out/language-server/workspaceContext/javaManager.js +710 -0
- package/out/language-server/workspaceContext/javaManager.js.map +1 -0
- package/out/language-server/workspaceContext/util.d.ts +10 -0
- package/out/language-server/workspaceContext/util.js +72 -0
- package/out/language-server/workspaceContext/util.js.map +1 -0
- package/out/language-server/workspaceContext/workspaceContextServer.d.ts +2 -0
- package/out/language-server/workspaceContext/workspaceContextServer.js +414 -0
- package/out/language-server/workspaceContext/workspaceContextServer.js.map +1 -0
- package/out/language-server/workspaceContext/workspaceFolderManager.d.ts +113 -0
- package/out/language-server/workspaceContext/workspaceFolderManager.js +827 -0
- package/out/language-server/workspaceContext/workspaceFolderManager.js.map +1 -0
- package/out/shared/amazonQServiceManager/qDeveloperProfiles.js.map +1 -1
- package/out/shared/codeWhispererService.d.ts +13 -0
- package/out/shared/codeWhispererService.js +24 -0
- package/out/shared/codeWhispererService.js.map +1 -1
- package/out/shared/languageDetection.d.ts +27 -0
- package/out/shared/languageDetection.js +44 -1
- package/out/shared/languageDetection.js.map +1 -1
- package/out/shared/localProjectContextController.d.ts +1 -0
- package/out/shared/localProjectContextController.js +6 -8
- package/out/shared/localProjectContextController.js.map +1 -1
- package/out/shared/proxy-server.d.ts +1 -0
- package/out/shared/proxy-server.js +3 -1
- package/out/shared/proxy-server.js.map +1 -1
- package/out/shared/supplementalContextUtil/crossFileContextUtil.d.ts +4 -3
- package/out/shared/supplementalContextUtil/crossFileContextUtil.js +16 -5
- package/out/shared/supplementalContextUtil/crossFileContextUtil.js.map +1 -1
- package/out/shared/supplementalContextUtil/supplementalContextUtil.d.ts +2 -1
- package/out/shared/supplementalContextUtil/supplementalContextUtil.js +2 -2
- package/out/shared/supplementalContextUtil/supplementalContextUtil.js.map +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkspaceFolderManager = void 0;
|
|
4
|
+
const client_1 = require("./client");
|
|
5
|
+
const util_1 = require("./util");
|
|
6
|
+
class WorkspaceFolderManager {
|
|
7
|
+
serviceManager;
|
|
8
|
+
logging;
|
|
9
|
+
artifactManager;
|
|
10
|
+
dependencyDiscoverer;
|
|
11
|
+
workspaceMap;
|
|
12
|
+
static instance;
|
|
13
|
+
workspaceFolders;
|
|
14
|
+
credentialsProvider;
|
|
15
|
+
INITIAL_CHECK_INTERVAL = 40 * 1000; // 40 seconds
|
|
16
|
+
INITIAL_TIMEOUT = 2 * 60 * 1000; // 2 minutes
|
|
17
|
+
CONTINUOUS_MONITOR_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
18
|
+
monitorIntervals = new Map();
|
|
19
|
+
isOptedOut = false;
|
|
20
|
+
static createInstance(serviceManager, logging, artifactManager, dependencyDiscoverer, workspaceFolders, credentialsProvider) {
|
|
21
|
+
if (!this.instance) {
|
|
22
|
+
this.instance = new WorkspaceFolderManager(serviceManager, logging, artifactManager, dependencyDiscoverer, workspaceFolders, credentialsProvider);
|
|
23
|
+
}
|
|
24
|
+
return this.instance;
|
|
25
|
+
}
|
|
26
|
+
static getInstance() {
|
|
27
|
+
return this.instance;
|
|
28
|
+
}
|
|
29
|
+
constructor(serviceManager, logging, artifactManager, dependencyDiscoverer, workspaceFolders, credentialsProvider) {
|
|
30
|
+
this.serviceManager = serviceManager;
|
|
31
|
+
this.logging = logging;
|
|
32
|
+
this.artifactManager = artifactManager;
|
|
33
|
+
this.dependencyDiscoverer = dependencyDiscoverer;
|
|
34
|
+
this.workspaceMap = new Map();
|
|
35
|
+
this.workspaceFolders = workspaceFolders;
|
|
36
|
+
this.credentialsProvider = credentialsProvider;
|
|
37
|
+
this.dependencyDiscoverer.dependencyHandlerRegistry.forEach(handler => {
|
|
38
|
+
handler.onDependencyChange(async (workspaceFolder, zips) => {
|
|
39
|
+
try {
|
|
40
|
+
this.logging.log(`Dependency change detected in ${workspaceFolder.uri}`);
|
|
41
|
+
// Process the dependencies
|
|
42
|
+
await this.handleDependencyChanges(zips);
|
|
43
|
+
// Clean up only after successful processing
|
|
44
|
+
await handler.cleanupZipFiles(zips);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
this.logging.warn(`Error handling dependency change: ${error}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The function is used to track the latest state of workspace folders.
|
|
54
|
+
* This state is updated irrespective of login/logout/optIn/optOut
|
|
55
|
+
* @param workspaceFolders
|
|
56
|
+
*/
|
|
57
|
+
updateWorkspaceFolders(workspaceFolders) {
|
|
58
|
+
this.workspaceFolders = workspaceFolders;
|
|
59
|
+
}
|
|
60
|
+
getWorkspaces() {
|
|
61
|
+
return this.workspaceMap;
|
|
62
|
+
}
|
|
63
|
+
getWorkspaceDetailsWithId(fileUri, workspaceFolders) {
|
|
64
|
+
const workspaceRoot = (0, util_1.findWorkspaceRootFolder)(fileUri, workspaceFolders ?? this.workspaceFolders);
|
|
65
|
+
if (!workspaceRoot) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const workspaceDetails = this.getWorkspaces().get(workspaceRoot.uri);
|
|
69
|
+
if (!workspaceDetails) {
|
|
70
|
+
this.logging.log(`Workspace details not found for workspace folder ${workspaceRoot.uri}`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (!workspaceDetails.workspaceId) {
|
|
74
|
+
this.logging.log(`Workspace initialization in progress - workspaceId not yet assigned for: ${workspaceRoot.uri}`);
|
|
75
|
+
return { workspaceDetails, workspaceRoot };
|
|
76
|
+
}
|
|
77
|
+
return { workspaceDetails, workspaceRoot };
|
|
78
|
+
}
|
|
79
|
+
getWorkspaceFolder(fileUri) {
|
|
80
|
+
return (0, util_1.findWorkspaceRootFolder)(fileUri, this.workspaceFolders);
|
|
81
|
+
}
|
|
82
|
+
getWorkspaceId(workspaceFolder) {
|
|
83
|
+
if (!workspaceFolder) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const workspaceDetails = this.getWorkspaces().get(workspaceFolder.uri);
|
|
87
|
+
if (!workspaceDetails || !workspaceDetails.workspaceId) {
|
|
88
|
+
this.logging.log(`Unable to retrieve workspaceId - workspace initialization incomplete for: ${workspaceFolder.uri}`);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
return workspaceDetails.workspaceId;
|
|
92
|
+
}
|
|
93
|
+
getOptOutStatus() {
|
|
94
|
+
return this.isOptedOut;
|
|
95
|
+
}
|
|
96
|
+
async processNewWorkspaceFolders(folders) {
|
|
97
|
+
// Check if user is opted in before trying to process any files
|
|
98
|
+
const { optOut } = await this.listWorkspaceMetadata();
|
|
99
|
+
if (optOut) {
|
|
100
|
+
this.logging.log('User is opted out, clearing resources and starting opt-out monitor');
|
|
101
|
+
this.isOptedOut = true;
|
|
102
|
+
await this.clearAllWorkspaceResources();
|
|
103
|
+
await this.startOptOutMonitor();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// CreateWorkspace and Setup state machine workflow
|
|
107
|
+
for (const folder of folders) {
|
|
108
|
+
await this.handleNewWorkspace(folder.uri).catch(e => {
|
|
109
|
+
this.logging.warn(`Error processing new workspace ${folder.uri} with error: ${e}`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Snapshot the workspace
|
|
113
|
+
this.snapshotWorkspace(folders).catch(e => {
|
|
114
|
+
this.logging.warn(`Error during snapshot workspace: ${e}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async snapshotWorkspace(folders) {
|
|
118
|
+
let sourceCodeMetadata = [];
|
|
119
|
+
sourceCodeMetadata = await this.artifactManager.addWorkspaceFolders(folders);
|
|
120
|
+
// Kick off dependency discovery but don't wait
|
|
121
|
+
this.dependencyDiscoverer.searchDependencies(folders).catch(e => {
|
|
122
|
+
this.logging.warn(`Error during dependency discovery: ${e}`);
|
|
123
|
+
});
|
|
124
|
+
const fileMetadataMap = new Map();
|
|
125
|
+
sourceCodeMetadata.forEach((fileMetadata) => {
|
|
126
|
+
let metadata = fileMetadataMap.get(fileMetadata.workspaceFolder.uri);
|
|
127
|
+
if (!metadata) {
|
|
128
|
+
metadata = [];
|
|
129
|
+
fileMetadataMap.set(fileMetadata.workspaceFolder.uri, metadata);
|
|
130
|
+
}
|
|
131
|
+
metadata.push(fileMetadata);
|
|
132
|
+
});
|
|
133
|
+
folders.forEach(folder => {
|
|
134
|
+
const workspaceDetails = this.getWorkspaces().get(folder.uri);
|
|
135
|
+
if (workspaceDetails) {
|
|
136
|
+
workspaceDetails.requiresS3Upload = true;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
await this.uploadWithTimeout(fileMetadataMap);
|
|
140
|
+
}
|
|
141
|
+
async uploadToS3(fileMetadata) {
|
|
142
|
+
let relativePath = fileMetadata.relativePath.replace(fileMetadata.workspaceFolder.name, '');
|
|
143
|
+
relativePath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
|
|
144
|
+
const workspaceId = this.getWorkspaces().get(fileMetadata.workspaceFolder.uri)?.workspaceId ?? '';
|
|
145
|
+
if (!workspaceId) {
|
|
146
|
+
this.logging.warn(`Workspace ID is not found for folder ${fileMetadata.workspaceFolder.uri}, skipping S3 upload`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let s3Url;
|
|
150
|
+
try {
|
|
151
|
+
const sha256 = await (0, util_1.getSha256Async)(fileMetadata.content);
|
|
152
|
+
const request = {
|
|
153
|
+
artifactType: 'SourceCode',
|
|
154
|
+
contentChecksumType: 'SHA_256',
|
|
155
|
+
contentChecksum: sha256,
|
|
156
|
+
uploadIntent: 'WORKSPACE_CONTEXT',
|
|
157
|
+
uploadContext: {
|
|
158
|
+
workspaceContextUploadContext: {
|
|
159
|
+
workspaceId: workspaceId,
|
|
160
|
+
relativePath: relativePath,
|
|
161
|
+
programmingLanguage: {
|
|
162
|
+
languageName: fileMetadata.language,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
const response = await this.serviceManager.getCodewhispererService().createUploadUrl(request);
|
|
168
|
+
s3Url = response.uploadUrl;
|
|
169
|
+
// Override upload id to be workspace id
|
|
170
|
+
await (0, util_1.uploadArtifactToS3)(Buffer.isBuffer(fileMetadata.content) ? fileMetadata.content : Buffer.from(fileMetadata.content), { ...response, uploadId: workspaceId });
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
this.logging.warn(`Error uploading file to S3: ${e.message}`);
|
|
174
|
+
}
|
|
175
|
+
return s3Url;
|
|
176
|
+
}
|
|
177
|
+
async clearAllWorkspaceResources() {
|
|
178
|
+
for (const workspace of this.monitorIntervals.keys()) {
|
|
179
|
+
this.stopMonitoring(workspace);
|
|
180
|
+
}
|
|
181
|
+
for (const { webSocketClient } of this.workspaceMap.values()) {
|
|
182
|
+
webSocketClient?.destroyClient();
|
|
183
|
+
}
|
|
184
|
+
this.workspaceMap.clear();
|
|
185
|
+
this.artifactManager.cleanup();
|
|
186
|
+
this.dependencyDiscoverer.dispose();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The function sends a removed workspace folders notification to remote LSP, removes workspace entry
|
|
190
|
+
* from map and close the websocket connection
|
|
191
|
+
* @param workspaceFolder
|
|
192
|
+
*/
|
|
193
|
+
async processWorkspaceFoldersDeletion(workspaceFolders) {
|
|
194
|
+
for (const folder of workspaceFolders) {
|
|
195
|
+
const workspaceDetails = this.workspaceMap.get(folder.uri);
|
|
196
|
+
const websocketClient = workspaceDetails?.webSocketClient;
|
|
197
|
+
const languagesMap = this.artifactManager.getLanguagesForWorkspaceFolder(folder);
|
|
198
|
+
const programmingLanguages = languagesMap ? Array.from(languagesMap.keys()) : [];
|
|
199
|
+
if (websocketClient) {
|
|
200
|
+
for (const language of programmingLanguages) {
|
|
201
|
+
// Wait for message being sent before disconnecting
|
|
202
|
+
await websocketClient
|
|
203
|
+
.send(JSON.stringify({
|
|
204
|
+
method: 'workspace/didChangeWorkspaceFolders',
|
|
205
|
+
params: {
|
|
206
|
+
workspaceFoldersChangeEvent: {
|
|
207
|
+
added: [],
|
|
208
|
+
removed: [
|
|
209
|
+
{
|
|
210
|
+
uri: '/',
|
|
211
|
+
name: folder.name,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
workspaceChangeMetadata: {
|
|
216
|
+
workspaceId: this.getWorkspaces().get(folder.uri)?.workspaceId ?? '',
|
|
217
|
+
programmingLanguage: language,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
}))
|
|
221
|
+
.catch(e => {
|
|
222
|
+
this.logging.error(`Error sending didChangeWorkspaceFolders message: ${e}`);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
websocketClient.disconnect();
|
|
226
|
+
}
|
|
227
|
+
this.removeWorkspaceEntry(folder.uri);
|
|
228
|
+
this.dependencyDiscoverer.disposeWorkspaceFolder(folder);
|
|
229
|
+
this.stopMonitoring(folder.uri);
|
|
230
|
+
}
|
|
231
|
+
await this.artifactManager.removeWorkspaceFolders(workspaceFolders);
|
|
232
|
+
}
|
|
233
|
+
processRemoteWorkspaceRefresh(workspaceFolders) {
|
|
234
|
+
for (const folder of workspaceFolders) {
|
|
235
|
+
const workspaceDetails = this.workspaceMap.get(folder.uri);
|
|
236
|
+
const websocketClient = workspaceDetails?.webSocketClient;
|
|
237
|
+
if (websocketClient) {
|
|
238
|
+
websocketClient.destroyClient();
|
|
239
|
+
}
|
|
240
|
+
this.removeWorkspaceEntry(folder.uri);
|
|
241
|
+
this.dependencyDiscoverer.disposeWorkspaceFolder(folder);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
updateWorkspaceEntry(workspaceRoot, workspaceState) {
|
|
245
|
+
if (!workspaceState.messageQueue) {
|
|
246
|
+
workspaceState.messageQueue = [];
|
|
247
|
+
}
|
|
248
|
+
if (!this.workspaceMap.has(workspaceRoot)) {
|
|
249
|
+
workspaceState.requiresS3Upload = true;
|
|
250
|
+
this.workspaceMap.set(workspaceRoot, workspaceState);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
const existingWorkspaceState = this.workspaceMap.get(workspaceRoot);
|
|
254
|
+
if (existingWorkspaceState) {
|
|
255
|
+
existingWorkspaceState.remoteWorkspaceState =
|
|
256
|
+
workspaceState.remoteWorkspaceState ?? existingWorkspaceState.remoteWorkspaceState;
|
|
257
|
+
existingWorkspaceState.webSocketClient =
|
|
258
|
+
workspaceState.webSocketClient ?? existingWorkspaceState.webSocketClient;
|
|
259
|
+
existingWorkspaceState.workspaceId = workspaceState.workspaceId ?? existingWorkspaceState.workspaceId;
|
|
260
|
+
existingWorkspaceState.messageQueue = workspaceState.messageQueue ?? existingWorkspaceState.messageQueue;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
removeWorkspaceEntry(workspaceRoot) {
|
|
265
|
+
this.workspaceMap.delete(workspaceRoot);
|
|
266
|
+
}
|
|
267
|
+
async handleDependencyChanges(zips) {
|
|
268
|
+
this.logging.log(`Processing ${zips.length} dependency changes`);
|
|
269
|
+
for (const zip of zips) {
|
|
270
|
+
try {
|
|
271
|
+
const s3Url = await this.uploadToS3(zip);
|
|
272
|
+
if (!s3Url) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
this.notifyDependencyChange(zip, s3Url);
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
this.logging.warn(`Error processing dependency change ${zip.filePath}: ${error}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
notifyDependencyChange(fileMetadata, s3Url) {
|
|
283
|
+
const workspaceDetails = this.getWorkspaces().get(fileMetadata.workspaceFolder.uri);
|
|
284
|
+
if (!workspaceDetails) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const message = JSON.stringify({
|
|
288
|
+
method: 'didChangeDependencyPaths',
|
|
289
|
+
params: {
|
|
290
|
+
event: { paths: [] },
|
|
291
|
+
workspaceChangeMetadata: {
|
|
292
|
+
workspaceId: workspaceDetails.workspaceId,
|
|
293
|
+
s3Path: (0, util_1.cleanUrl)(s3Url),
|
|
294
|
+
programmingLanguage: fileMetadata.language,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
if (!workspaceDetails.webSocketClient) {
|
|
299
|
+
this.logging.log(`WebSocket client is not connected yet: ${fileMetadata.workspaceFolder.uri} adding didChangeDependencyPaths message to queue`);
|
|
300
|
+
workspaceDetails.messageQueue?.push(message);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
workspaceDetails.webSocketClient.send(message).catch(e => {
|
|
304
|
+
this.logging.error(`Error sending didChangeDependencyPaths message: ${e}`);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async createNewWorkspace(workspace) {
|
|
309
|
+
const createWorkspaceResult = await this.createWorkspace(workspace);
|
|
310
|
+
const workspaceDetails = createWorkspaceResult.response;
|
|
311
|
+
if (!workspaceDetails) {
|
|
312
|
+
this.logging.warn(`Failed to create remote workspace for ${workspace}`);
|
|
313
|
+
return createWorkspaceResult;
|
|
314
|
+
}
|
|
315
|
+
this.updateWorkspaceEntry(workspace, {
|
|
316
|
+
remoteWorkspaceState: workspaceDetails.workspace.workspaceStatus,
|
|
317
|
+
workspaceId: workspaceDetails.workspace.workspaceId,
|
|
318
|
+
});
|
|
319
|
+
return createWorkspaceResult;
|
|
320
|
+
}
|
|
321
|
+
async establishConnection(workspace, existingMetadata) {
|
|
322
|
+
const existingState = this.workspaceMap.get(workspace);
|
|
323
|
+
if (!existingMetadata.environmentId) {
|
|
324
|
+
throw new Error('No environment ID found for ready workspace');
|
|
325
|
+
}
|
|
326
|
+
if (!existingMetadata.environmentAddress) {
|
|
327
|
+
throw new Error('No environment address found for ready workspace');
|
|
328
|
+
}
|
|
329
|
+
const websocketUrl = existingMetadata.environmentAddress;
|
|
330
|
+
this.logging.log(`Establishing connection to ${websocketUrl}`);
|
|
331
|
+
if (existingState?.webSocketClient) {
|
|
332
|
+
const websocketConnectionState = existingState.webSocketClient.getWebsocketReadyState();
|
|
333
|
+
if (websocketConnectionState === 'OPEN') {
|
|
334
|
+
this.logging.log(`Active connection already exists for ${workspace}`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// If the client exists but isn't connected, it might be in the process of connecting
|
|
338
|
+
if (websocketConnectionState === 'CONNECTING') {
|
|
339
|
+
this.logging.log(`Connection attempt already in progress for ${workspace}`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const webSocketClient = new client_1.WebSocketClient(websocketUrl, this.logging, this.credentialsProvider);
|
|
344
|
+
this.updateWorkspaceEntry(workspace, {
|
|
345
|
+
remoteWorkspaceState: 'CONNECTED',
|
|
346
|
+
webSocketClient,
|
|
347
|
+
messageQueue: existingState?.messageQueue || [],
|
|
348
|
+
});
|
|
349
|
+
await this.processMessagesInQueue(workspace);
|
|
350
|
+
}
|
|
351
|
+
async handleNewWorkspace(workspace, queueEvents) {
|
|
352
|
+
this.logging.log(`Processing new workspace ${workspace}`);
|
|
353
|
+
// First check if the workspace already exists in the workspace map
|
|
354
|
+
const existingWorkspace = this.workspaceMap.get(workspace);
|
|
355
|
+
if (existingWorkspace) {
|
|
356
|
+
this.logging.log(`Workspace ${workspace} already exists in memory`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
// Check if workspace exists remotely
|
|
360
|
+
const { metadata, optOut } = await this.listWorkspaceMetadata(workspace);
|
|
361
|
+
if (optOut) {
|
|
362
|
+
this.logging.log(`Not creating a new workspace ${workspace}, user is opted out`);
|
|
363
|
+
this.isOptedOut = true;
|
|
364
|
+
await this.clearAllWorkspaceResources();
|
|
365
|
+
await this.startOptOutMonitor();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (metadata) {
|
|
369
|
+
// Workspace exists remotely, add to map with current state
|
|
370
|
+
this.updateWorkspaceEntry(workspace, {
|
|
371
|
+
workspaceId: metadata.workspaceId,
|
|
372
|
+
remoteWorkspaceState: metadata.workspaceStatus,
|
|
373
|
+
messageQueue: queueEvents ?? [],
|
|
374
|
+
});
|
|
375
|
+
// We don't attempt a connection here even if remote workspace is ready and leave the connection attempt to the workspace status monitor
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Create new workspace
|
|
379
|
+
const createWorkspaceResult = await this.createNewWorkspace(workspace);
|
|
380
|
+
if (createWorkspaceResult.error && createWorkspaceResult.error.retryable) {
|
|
381
|
+
this.logging.log(`Workspace creation failed with retryable error, starting monitor`);
|
|
382
|
+
// todo, consider whether we should add the failed env to the map or not and what to do in the create failure case
|
|
383
|
+
this.updateWorkspaceEntry(workspace, {
|
|
384
|
+
remoteWorkspaceState: 'CREATION_PENDING',
|
|
385
|
+
messageQueue: queueEvents ?? [],
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
this.startWorkspaceStatusMonitor(workspace).catch(error => {
|
|
390
|
+
this.logging.error(`Error starting workspace monitor for ${workspace}: ${error}`);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async waitForInitialConnection(workspace) {
|
|
394
|
+
this.logging.log(`Waiting for initial connection to ${workspace}`);
|
|
395
|
+
return new Promise(resolve => {
|
|
396
|
+
const startTime = Date.now();
|
|
397
|
+
const intervalId = setInterval(async () => {
|
|
398
|
+
try {
|
|
399
|
+
const workspaceState = this.workspaceMap.get(workspace);
|
|
400
|
+
if (!workspaceState) {
|
|
401
|
+
this.logging.log(`Workspace ${workspace} no longer exists, stopping monitors for workspace`);
|
|
402
|
+
clearInterval(intervalId);
|
|
403
|
+
return resolve(false);
|
|
404
|
+
}
|
|
405
|
+
const { metadata, optOut } = await this.listWorkspaceMetadata(workspace);
|
|
406
|
+
if (optOut) {
|
|
407
|
+
this.logging.log(`User opted out during initial connection`);
|
|
408
|
+
this.isOptedOut = true;
|
|
409
|
+
await this.clearAllWorkspaceResources();
|
|
410
|
+
await this.startOptOutMonitor();
|
|
411
|
+
return resolve(false);
|
|
412
|
+
}
|
|
413
|
+
if (!metadata) {
|
|
414
|
+
// Continue polling by exiting only this iteration
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
this.updateWorkspaceEntry(workspace, {
|
|
418
|
+
...workspaceState,
|
|
419
|
+
remoteWorkspaceState: metadata.workspaceStatus,
|
|
420
|
+
});
|
|
421
|
+
switch (metadata.workspaceStatus) {
|
|
422
|
+
case 'READY':
|
|
423
|
+
const client = workspaceState.webSocketClient;
|
|
424
|
+
if (!client || !client.isConnected()) {
|
|
425
|
+
await this.establishConnection(workspace, metadata);
|
|
426
|
+
}
|
|
427
|
+
clearInterval(intervalId);
|
|
428
|
+
return resolve(true);
|
|
429
|
+
case 'PENDING':
|
|
430
|
+
// Continue polling
|
|
431
|
+
break;
|
|
432
|
+
case 'CREATED':
|
|
433
|
+
const createWorkspaceResult = await this.createNewWorkspace(workspace);
|
|
434
|
+
// If createWorkspace call returns a retyrable error, next interval will retry
|
|
435
|
+
// If the call returns non-retryable error, we will re-throw the error and it will stop the interval
|
|
436
|
+
if (createWorkspaceResult.error && !createWorkspaceResult.error.retryable) {
|
|
437
|
+
throw createWorkspaceResult.error.originalError;
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
default:
|
|
441
|
+
this.logging.warn(`Unknown workspace status: ${metadata.workspaceStatus}`);
|
|
442
|
+
clearInterval(intervalId);
|
|
443
|
+
return resolve(false);
|
|
444
|
+
}
|
|
445
|
+
if (Date.now() - startTime >= this.INITIAL_TIMEOUT) {
|
|
446
|
+
this.logging.warn(`Initial connection timeout for workspace ${workspace}`);
|
|
447
|
+
clearInterval(intervalId);
|
|
448
|
+
return resolve(false);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
this.logging.error(`Error during initial connection for workspace ${workspace}: ${error}`);
|
|
453
|
+
clearInterval(intervalId);
|
|
454
|
+
return resolve(false);
|
|
455
|
+
}
|
|
456
|
+
}, this.INITIAL_CHECK_INTERVAL);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
startContinuousMonitor(workspace) {
|
|
460
|
+
this.logging.log(`Starting continuous monitor for workspace ${workspace}`);
|
|
461
|
+
const intervalId = setInterval(async () => {
|
|
462
|
+
try {
|
|
463
|
+
const workspaceState = this.workspaceMap.get(workspace);
|
|
464
|
+
if (!workspaceState) {
|
|
465
|
+
// Previously we stop monitoring the workspace if it no longer exists
|
|
466
|
+
// But now we want to try give it a chance to re-create the workspace
|
|
467
|
+
// this.stopMonitoring(workspace)
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const { metadata, optOut } = await this.listWorkspaceMetadata(workspace);
|
|
471
|
+
if (optOut) {
|
|
472
|
+
this.logging.log('User opted out, clearing all resources and starting opt-out monitor');
|
|
473
|
+
this.isOptedOut = true;
|
|
474
|
+
await this.clearAllWorkspaceResources();
|
|
475
|
+
await this.startOptOutMonitor();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (!metadata) {
|
|
479
|
+
// Workspace no longer exists, Recreate it.
|
|
480
|
+
await this.handleWorkspaceCreatedState(workspace);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.updateWorkspaceEntry(workspace, {
|
|
484
|
+
...workspaceState,
|
|
485
|
+
remoteWorkspaceState: metadata.workspaceStatus,
|
|
486
|
+
});
|
|
487
|
+
switch (metadata.workspaceStatus) {
|
|
488
|
+
case 'READY':
|
|
489
|
+
// Check if connection exists
|
|
490
|
+
const client = workspaceState.webSocketClient;
|
|
491
|
+
if (!client || !client.isConnected()) {
|
|
492
|
+
this.logging.log(`Workspace ${workspace} is ready but no connection exists or connection lost. Re-establishing connection...`);
|
|
493
|
+
await this.establishConnection(workspace, metadata);
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
case 'PENDING':
|
|
497
|
+
// Do nothing while pending
|
|
498
|
+
break;
|
|
499
|
+
case 'CREATED':
|
|
500
|
+
// Workspace has no environment, Recreate it.
|
|
501
|
+
await this.handleWorkspaceCreatedState(workspace);
|
|
502
|
+
break;
|
|
503
|
+
default:
|
|
504
|
+
this.logging.warn(`Unknown workspace status: ${metadata.workspaceStatus}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
this.logging.error(`Error monitoring workspace ${workspace}: ${error}`);
|
|
509
|
+
}
|
|
510
|
+
}, this.CONTINUOUS_MONITOR_INTERVAL);
|
|
511
|
+
this.monitorIntervals.set(workspace, intervalId);
|
|
512
|
+
}
|
|
513
|
+
async startOptOutMonitor() {
|
|
514
|
+
const intervalId = setInterval(async () => {
|
|
515
|
+
try {
|
|
516
|
+
const { optOut } = await this.listWorkspaceMetadata();
|
|
517
|
+
if (!optOut) {
|
|
518
|
+
this.isOptedOut = false;
|
|
519
|
+
this.logging.log('User opted back in, stopping opt-out monitor and re-initializing workspace');
|
|
520
|
+
clearInterval(intervalId);
|
|
521
|
+
// Process all workspace folders
|
|
522
|
+
await this.processNewWorkspaceFolders(this.workspaceFolders);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
this.logging.error(`Error in opt-out monitor: ${error}`);
|
|
527
|
+
}
|
|
528
|
+
}, this.CONTINUOUS_MONITOR_INTERVAL);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Handles the workspace creation flow when a remote workspace is in CREATED state.
|
|
532
|
+
* Attempts to create the workspace and schedules a quick connection check on success.
|
|
533
|
+
* If the initial creation fails with a retryable error, attempts one retry before
|
|
534
|
+
* falling back to the regular polling cycle.
|
|
535
|
+
*
|
|
536
|
+
* The flow is:
|
|
537
|
+
* 1. Attempt initial workspace creation
|
|
538
|
+
* 2. On success: Schedule a quick check to establish connection
|
|
539
|
+
* 3. On retryable error: Attempt one immediate retry
|
|
540
|
+
* 4. On retry success: Schedule a quick check
|
|
541
|
+
* 5. On retry failure: Wait for next regular polling cycle
|
|
542
|
+
*
|
|
543
|
+
* @param workspace - The workspace to re-create
|
|
544
|
+
*/
|
|
545
|
+
async handleWorkspaceCreatedState(workspace) {
|
|
546
|
+
// If remote state is CREATED, call create API to create a new workspace
|
|
547
|
+
// snapshot the workspace for the new environment
|
|
548
|
+
// TODO: this workspace root in the below snapshot function needs to be changes
|
|
549
|
+
// after we consolidate workspaceFolder into one Workspace.
|
|
550
|
+
const folder = this.getWorkspaceFolder(workspace);
|
|
551
|
+
if (folder) {
|
|
552
|
+
this.processRemoteWorkspaceRefresh([folder]);
|
|
553
|
+
}
|
|
554
|
+
const initialResult = await this.createNewWorkspace(workspace);
|
|
555
|
+
if (folder) {
|
|
556
|
+
this.snapshotWorkspace([folder]).catch(e => {
|
|
557
|
+
this.logging.warn(`Error during snapshot workspace: ${e}`);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
// If creation succeeds, schedule a single connection attempt to happen in 30 seconds
|
|
561
|
+
if (initialResult.response) {
|
|
562
|
+
this.logging.log(`Workspace ${workspace} created successfully, scheduling quick check for connection`);
|
|
563
|
+
this.scheduleQuickCheck(workspace);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
// If creation fails with a non-retryable error, don't do anything
|
|
567
|
+
// Continuous monitor will evaluate the status again in 5 minutes
|
|
568
|
+
if (!initialResult.error?.retryable) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
this.logging.warn(`Retryable error for workspace ${workspace} creation: ${initialResult.error}. Attempting single retry...`);
|
|
572
|
+
const retryResult = await this.createNewWorkspace(workspace);
|
|
573
|
+
if (retryResult.error) {
|
|
574
|
+
this.logging.warn(`Retry failed for workspace ${workspace}: ${retryResult.error}. Will wait for next polling cycle`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
this.logging.log(`Retry succeeded for workspace ${workspace}, scheduling quick check for connection`);
|
|
578
|
+
this.scheduleQuickCheck(workspace);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Schedules a one-time check after workspace creation to establish connection as soon as possible.
|
|
582
|
+
* This avoids waiting for the regular 5-minute polling interval when we know the workspace
|
|
583
|
+
* should be ready soon. Default check delay is 40 seconds after successful workspace creation.
|
|
584
|
+
*
|
|
585
|
+
* @param workspace - The workspace to check
|
|
586
|
+
* @param delayMs - Optional delay in milliseconds before the check (defaults to 40 seconds)
|
|
587
|
+
*/
|
|
588
|
+
scheduleQuickCheck(workspace, delayMs = this.INITIAL_CHECK_INTERVAL) {
|
|
589
|
+
this.logging.log(`Scheduling quick check for workspace ${workspace} in ${delayMs}ms`);
|
|
590
|
+
setTimeout(async () => {
|
|
591
|
+
try {
|
|
592
|
+
const workspaceState = this.workspaceMap.get(workspace);
|
|
593
|
+
if (!workspaceState) {
|
|
594
|
+
this.logging.log(`Workspace state not found for: ${workspace} during quick check`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const { metadata, optOut } = await this.listWorkspaceMetadata(workspace);
|
|
598
|
+
if (optOut) {
|
|
599
|
+
this.logging.log(`User is opted out during quick check`);
|
|
600
|
+
this.isOptedOut = true;
|
|
601
|
+
await this.clearAllWorkspaceResources();
|
|
602
|
+
await this.startOptOutMonitor();
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (!metadata) {
|
|
606
|
+
this.logging.log(`No metadata available for workspace: ${workspace} during quick check`);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (metadata.workspaceStatus === 'READY') {
|
|
610
|
+
this.logging.log(`Quick check found workspace ${workspace} is ready, attempting connection`);
|
|
611
|
+
await this.establishConnection(workspace, metadata);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
this.logging.log(`Quick check found workspace ${workspace} state is ${metadata.workspaceStatus}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
this.logging.error(`Error during quick check for workspace ${workspace}: ${error}`);
|
|
619
|
+
}
|
|
620
|
+
}, delayMs);
|
|
621
|
+
}
|
|
622
|
+
stopMonitoring(workspace) {
|
|
623
|
+
this.logging.log(`Stopping monitoring for workspace ${workspace}`);
|
|
624
|
+
const intervalId = this.monitorIntervals.get(workspace);
|
|
625
|
+
if (intervalId) {
|
|
626
|
+
clearInterval(intervalId);
|
|
627
|
+
this.monitorIntervals.delete(workspace);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async startWorkspaceStatusMonitor(workspace) {
|
|
631
|
+
const success = await this.waitForInitialConnection(workspace);
|
|
632
|
+
this.logging.log(`Initial connection ${success ? 'successful' : 'failed'} for ${workspace}, starting continuous monitor`);
|
|
633
|
+
if (!success && this.isOptedOut) {
|
|
634
|
+
// If initial connection fails due to opt out, do not start the continuous monitor
|
|
635
|
+
// The opt-out monitor will already be started
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.startContinuousMonitor(workspace);
|
|
639
|
+
}
|
|
640
|
+
// could this cause messages to be lost??????
|
|
641
|
+
async processMessagesInQueue(workspaceRoot) {
|
|
642
|
+
const workspaceDetails = this.workspaceMap.get(workspaceRoot);
|
|
643
|
+
while (workspaceDetails?.messageQueue && workspaceDetails.messageQueue.length > 0) {
|
|
644
|
+
const message = workspaceDetails.messageQueue.shift();
|
|
645
|
+
await workspaceDetails.webSocketClient?.send(message).catch(error => {
|
|
646
|
+
this.logging.error(`Error sending message: ${error}`);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* All the filesMetadata elements passed to the function belongs to the same workspace folder.
|
|
652
|
+
* @param filesMetadata
|
|
653
|
+
* @private
|
|
654
|
+
*/
|
|
655
|
+
async uploadS3AndQueueEvents(filesMetadata) {
|
|
656
|
+
if (filesMetadata.length == 0) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const inMemoryQueueEvents = [];
|
|
660
|
+
for (const fileMetadata of filesMetadata) {
|
|
661
|
+
try {
|
|
662
|
+
const s3Url = await this.uploadToS3(fileMetadata);
|
|
663
|
+
if (!s3Url) {
|
|
664
|
+
this.logging.warn(`Failed to get S3 URL for file in workspace: ${fileMetadata.workspaceFolder.name}`);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
this.logging.log(`Successfully uploaded to S3: workspace=${fileMetadata.workspaceFolder.name} language=${fileMetadata.language}`);
|
|
668
|
+
const workspaceId = this.getWorkspaces().get(fileMetadata.workspaceFolder.uri)?.workspaceId;
|
|
669
|
+
if (!workspaceId) {
|
|
670
|
+
this.logging.warn(`No workspace ID found for URI: ${fileMetadata.workspaceFolder.uri}`);
|
|
671
|
+
}
|
|
672
|
+
const event = JSON.stringify({
|
|
673
|
+
method: 'workspace/didChangeWorkspaceFolders',
|
|
674
|
+
params: {
|
|
675
|
+
workspaceFoldersChangeEvent: {
|
|
676
|
+
added: [
|
|
677
|
+
{
|
|
678
|
+
uri: '/',
|
|
679
|
+
name: fileMetadata.workspaceFolder.name,
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
removed: [],
|
|
683
|
+
},
|
|
684
|
+
workspaceChangeMetadata: {
|
|
685
|
+
workspaceId: workspaceId ?? '',
|
|
686
|
+
s3Path: (0, util_1.cleanUrl)(s3Url),
|
|
687
|
+
programmingLanguage: fileMetadata.language,
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
// We add this event to the front of the queue here to prevent any race condition that might put events before the didChangeWorkspaceFolders event
|
|
692
|
+
inMemoryQueueEvents.unshift(event);
|
|
693
|
+
this.logging.log(`Added didChangeWorkspaceFolders event to queue`);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
this.logging.error(`Error processing file metadata:${error instanceof Error ? error.message : 'Unknown error'}, workspace=${fileMetadata.workspaceFolder.name}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
try {
|
|
700
|
+
const workspaceDetails = this.getWorkspaces().get(filesMetadata[0].workspaceFolder.uri);
|
|
701
|
+
if (!workspaceDetails) {
|
|
702
|
+
this.logging.error(`No workspace details found for URI: ${filesMetadata[0].workspaceFolder.uri}`);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (workspaceDetails.webSocketClient) {
|
|
706
|
+
inMemoryQueueEvents.forEach((event, index) => {
|
|
707
|
+
try {
|
|
708
|
+
workspaceDetails.webSocketClient?.send(event).catch(error => {
|
|
709
|
+
this.logging.error(`Error sending event: ${error instanceof Error ? error.message : 'Unknown error'}, eventIndex=${index}`);
|
|
710
|
+
});
|
|
711
|
+
this.logging.log(`Successfully sent event ${index + 1}/${inMemoryQueueEvents.length}`);
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
this.logging.error(`Failed to send event via WebSocket:${error instanceof Error ? error.message : 'Unknown error'}, eventIndex=${index}`);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
if (workspaceDetails.messageQueue) {
|
|
720
|
+
workspaceDetails.messageQueue.push(...inMemoryQueueEvents);
|
|
721
|
+
this.logging.log(`Added ${inMemoryQueueEvents.length} events to message queue`);
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
this.logging.warn('No message queue available to store events');
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
this.logging.error(`Error in final processing: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
730
|
+
}
|
|
731
|
+
this.logging.log(`Completed processing ${inMemoryQueueEvents.length} queued WebSocket events`);
|
|
732
|
+
}
|
|
733
|
+
async uploadWithTimeout(fileMetadataMap) {
|
|
734
|
+
const keys = [...fileMetadataMap.keys()];
|
|
735
|
+
const totalWorkspaces = keys.length;
|
|
736
|
+
let workspacesWithS3UploadComplete = 0;
|
|
737
|
+
for (const key of keys) {
|
|
738
|
+
const workspaceDetails = this.getWorkspaces().get(key);
|
|
739
|
+
if (!workspaceDetails) {
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (workspaceDetails.workspaceId && workspaceDetails.requiresS3Upload) {
|
|
743
|
+
this.logging.log(`Starting S3 upload for ${key}, workspace id: ${workspaceDetails.workspaceId}`);
|
|
744
|
+
await this.uploadS3AndQueueEvents(fileMetadataMap.get(key) ?? []);
|
|
745
|
+
workspaceDetails.requiresS3Upload = false;
|
|
746
|
+
}
|
|
747
|
+
// This if condition needs to be separate because workspacesWithS3UploadComplete variable is set to 0 every time this function is called
|
|
748
|
+
// If this function is run once and uploads some of the workspace folders, we need to ensure we don't forget about already uploaded folders the next time the function is run
|
|
749
|
+
if (workspaceDetails.workspaceId) {
|
|
750
|
+
workspacesWithS3UploadComplete++;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (totalWorkspaces !== workspacesWithS3UploadComplete) {
|
|
754
|
+
// Schedule next check if not all workspaces are complete
|
|
755
|
+
// Notice that we don't await the uploadWithTimeout now, it is fire and forget at the moment
|
|
756
|
+
setTimeout(() => this.uploadWithTimeout(fileMetadataMap), 3000);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
this.logging.log(`All workspaces with S3 upload complete`);
|
|
760
|
+
// Clean up source code zip files after S3 upload
|
|
761
|
+
// Preserve dependencies because they might still be processing
|
|
762
|
+
// LanguageDependencyHandler is responsible for deleting dependency zips
|
|
763
|
+
this.artifactManager.cleanup(true);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// TODO, this function is unused at the moment
|
|
767
|
+
async deleteWorkspace(workspaceId) {
|
|
768
|
+
try {
|
|
769
|
+
if ((0, util_1.isLoggedInUsingBearerToken)(this.credentialsProvider)) {
|
|
770
|
+
await this.serviceManager.getCodewhispererService().deleteWorkspace({
|
|
771
|
+
workspaceId: workspaceId,
|
|
772
|
+
});
|
|
773
|
+
this.logging.log(`Workspace (${workspaceId}) deleted successfully`);
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
this.logging.log(`Skipping workspace (${workspaceId}) deletion because user is not logged in`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
catch (e) {
|
|
780
|
+
this.logging.warn(`Error while deleting workspace (${workspaceId}): ${e.message}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* The function fetches remote workspace metadata. There'll always be single entry for workspace
|
|
785
|
+
* metadata in the response, so intentionally picking the first index element.
|
|
786
|
+
* @param workspaceRoot
|
|
787
|
+
* @private
|
|
788
|
+
*/
|
|
789
|
+
async listWorkspaceMetadata(workspaceRoot) {
|
|
790
|
+
let metadata;
|
|
791
|
+
let optOut = false;
|
|
792
|
+
try {
|
|
793
|
+
const params = workspaceRoot ? { workspaceRoot } : {};
|
|
794
|
+
const response = await this.serviceManager.getCodewhispererService().listWorkspaceMetadata(params);
|
|
795
|
+
metadata = response && response.workspaces.length ? response.workspaces[0] : null;
|
|
796
|
+
}
|
|
797
|
+
catch (e) {
|
|
798
|
+
this.logging.warn(`Error while fetching workspace (${workspaceRoot}) metadata: ${e?.message}`);
|
|
799
|
+
if (e?.__type?.includes('AccessDeniedException') &&
|
|
800
|
+
e?.reason === 'UNAUTHORIZED_WORKSPACE_CONTEXT_FEATURE_ACCESS') {
|
|
801
|
+
this.logging.log(`Server side opt-out detected for workspace context`);
|
|
802
|
+
optOut = true;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return { metadata, optOut };
|
|
806
|
+
}
|
|
807
|
+
async createWorkspace(workspaceRoot) {
|
|
808
|
+
let response;
|
|
809
|
+
try {
|
|
810
|
+
response = await this.serviceManager.getCodewhispererService().createWorkspace({
|
|
811
|
+
workspaceRoot: workspaceRoot,
|
|
812
|
+
});
|
|
813
|
+
return { response, error: null };
|
|
814
|
+
}
|
|
815
|
+
catch (e) {
|
|
816
|
+
this.logging.warn(`Error while creating workspace (${workspaceRoot}): ${e.message}. Error is ${e.retryable ? '' : 'not'} retryable}`);
|
|
817
|
+
const error = {
|
|
818
|
+
message: e.message,
|
|
819
|
+
retryable: e.retryable ?? false,
|
|
820
|
+
originalError: e,
|
|
821
|
+
};
|
|
822
|
+
return { response: null, error };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
exports.WorkspaceFolderManager = WorkspaceFolderManager;
|
|
827
|
+
//# sourceMappingURL=workspaceFolderManager.js.map
|