@theia/ai-chat 1.66.0-next.67 → 1.66.0-next.73
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/lib/browser/agent-delegation-tool.d.ts.map +1 -1
- package/lib/browser/agent-delegation-tool.js +4 -2
- package/lib/browser/agent-delegation-tool.js.map +1 -1
- package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +15 -0
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/browser/change-set-file-element-deserializer.d.ts +7 -0
- package/lib/browser/change-set-file-element-deserializer.d.ts.map +1 -0
- package/lib/browser/change-set-file-element-deserializer.js +61 -0
- package/lib/browser/change-set-file-element-deserializer.js.map +1 -0
- package/lib/browser/change-set-file-element.d.ts +2 -0
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +17 -0
- package/lib/browser/change-set-file-element.js.map +1 -1
- package/lib/browser/chat-session-store-impl.d.ts +36 -0
- package/lib/browser/chat-session-store-impl.d.ts.map +1 -0
- package/lib/browser/chat-session-store-impl.js +287 -0
- package/lib/browser/chat-session-store-impl.js.map +1 -0
- package/lib/common/change-set-element-deserializer.d.ts +30 -0
- package/lib/common/change-set-element-deserializer.d.ts.map +1 -0
- package/lib/common/change-set-element-deserializer.js +81 -0
- package/lib/common/change-set-element-deserializer.js.map +1 -0
- package/lib/common/change-set.d.ts +7 -0
- package/lib/common/change-set.d.ts.map +1 -1
- package/lib/common/change-set.js.map +1 -1
- package/lib/common/chat-auto-save.spec.d.ts +2 -0
- package/lib/common/chat-auto-save.spec.d.ts.map +1 -0
- package/lib/common/chat-auto-save.spec.js +304 -0
- package/lib/common/chat-auto-save.spec.js.map +1 -0
- package/lib/common/chat-content-deserializer.d.ts +161 -0
- package/lib/common/chat-content-deserializer.d.ts.map +1 -0
- package/lib/common/chat-content-deserializer.js +166 -0
- package/lib/common/chat-content-deserializer.js.map +1 -0
- package/lib/common/chat-content-deserializer.spec.d.ts +2 -0
- package/lib/common/chat-content-deserializer.spec.d.ts.map +1 -0
- package/lib/common/chat-content-deserializer.spec.js +307 -0
- package/lib/common/chat-content-deserializer.spec.js.map +1 -0
- package/lib/common/chat-model-serialization.d.ts +110 -0
- package/lib/common/chat-model-serialization.d.ts.map +1 -0
- package/lib/common/chat-model-serialization.js +20 -0
- package/lib/common/chat-model-serialization.js.map +1 -0
- package/lib/common/chat-model-serialization.spec.d.ts +2 -0
- package/lib/common/chat-model-serialization.spec.d.ts.map +1 -0
- package/lib/common/chat-model-serialization.spec.js +278 -0
- package/lib/common/chat-model-serialization.spec.js.map +1 -0
- package/lib/common/chat-model.d.ts +163 -14
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +444 -36
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/chat-service-deletion.spec.d.ts +2 -0
- package/lib/common/chat-service-deletion.spec.d.ts.map +1 -0
- package/lib/common/chat-service-deletion.spec.js +221 -0
- package/lib/common/chat-service-deletion.spec.js.map +1 -0
- package/lib/common/chat-service.d.ts +30 -2
- package/lib/common/chat-service.d.ts.map +1 -1
- package/lib/common/chat-service.js +182 -10
- package/lib/common/chat-service.js.map +1 -1
- package/lib/common/chat-session-store.d.ts +43 -0
- package/lib/common/chat-session-store.d.ts.map +1 -0
- package/lib/common/chat-session-store.js +20 -0
- package/lib/common/chat-session-store.js.map +1 -0
- package/lib/common/index.d.ts +3 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +3 -0
- package/lib/common/index.js.map +1 -1
- package/package.json +9 -9
- package/src/browser/agent-delegation-tool.ts +4 -2
- package/src/browser/ai-chat-frontend-module.ts +27 -0
- package/src/browser/change-set-file-element-deserializer.ts +62 -0
- package/src/browser/change-set-file-element.ts +19 -0
- package/src/browser/chat-session-store-impl.ts +326 -0
- package/src/common/change-set-element-deserializer.ts +90 -0
- package/src/common/change-set.ts +8 -0
- package/src/common/chat-auto-save.spec.ts +372 -0
- package/src/common/chat-content-deserializer.spec.ts +375 -0
- package/src/common/chat-content-deserializer.ts +327 -0
- package/src/common/chat-model-serialization.spec.ts +343 -0
- package/src/common/chat-model-serialization.ts +133 -0
- package/src/common/chat-model.ts +644 -41
- package/src/common/chat-service-deletion.spec.ts +269 -0
- package/src/common/chat-service.ts +227 -10
- package/src/common/chat-session-store.ts +63 -0
- package/src/common/index.ts +3 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
18
|
+
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
19
|
+
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
20
|
+
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
21
|
+
import { StorageService } from '@theia/core/lib/browser';
|
|
22
|
+
import { URI } from '@theia/core';
|
|
23
|
+
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
|
|
24
|
+
import { ILogger } from '@theia/core/lib/common/logger';
|
|
25
|
+
import { ChatModel } from '../common/chat-model';
|
|
26
|
+
import { ChatSessionIndex, ChatSessionStore, ChatModelWithMetadata, ChatSessionMetadata } from '../common/chat-session-store';
|
|
27
|
+
import { SerializedChatData, CHAT_DATA_VERSION } from '../common/chat-model-serialization';
|
|
28
|
+
|
|
29
|
+
const MAX_SESSIONS = 25;
|
|
30
|
+
const INDEX_FILE = 'index.json';
|
|
31
|
+
|
|
32
|
+
@injectable()
|
|
33
|
+
export class ChatSessionStoreImpl implements ChatSessionStore {
|
|
34
|
+
@inject(FileService)
|
|
35
|
+
protected readonly fileService: FileService;
|
|
36
|
+
|
|
37
|
+
@inject(WorkspaceService)
|
|
38
|
+
protected readonly workspaceService: WorkspaceService;
|
|
39
|
+
|
|
40
|
+
@inject(EnvVariablesServer)
|
|
41
|
+
protected readonly envServer: EnvVariablesServer;
|
|
42
|
+
|
|
43
|
+
@inject(StorageService)
|
|
44
|
+
protected readonly storageService: StorageService;
|
|
45
|
+
|
|
46
|
+
@inject(ILogger) @named('ChatSessionStore')
|
|
47
|
+
protected readonly logger: ILogger;
|
|
48
|
+
|
|
49
|
+
protected storageRoot?: URI;
|
|
50
|
+
protected indexCache?: ChatSessionIndex;
|
|
51
|
+
protected storePromise: Promise<void> = Promise.resolve();
|
|
52
|
+
|
|
53
|
+
async storeSessions(...sessions: Array<ChatModel | ChatModelWithMetadata>): Promise<void> {
|
|
54
|
+
this.storePromise = this.storePromise.then(async () => {
|
|
55
|
+
const root = await this.getStorageRoot();
|
|
56
|
+
this.logger.debug('Starting to store sessions', { totalSessions: sessions.length, storageRoot: root.toString() });
|
|
57
|
+
|
|
58
|
+
// Normalize to SessionWithTitle and filter empty sessions
|
|
59
|
+
const nonEmptySessions = sessions
|
|
60
|
+
.map(s => this.isChatModelWithMetadata(s) ? { ...s, saveDate: Date.now() } : { model: s, saveDate: Date.now() })
|
|
61
|
+
.filter(s => !s.model.isEmpty());
|
|
62
|
+
this.logger.debug('Filtered empty sessions', { nonEmptySessions: nonEmptySessions.length });
|
|
63
|
+
|
|
64
|
+
// Write each session as JSON file
|
|
65
|
+
for (const session of nonEmptySessions) {
|
|
66
|
+
const sessionFile = root.resolve(`${session.model.id}.json`);
|
|
67
|
+
const modelData = session.model.toSerializable();
|
|
68
|
+
// Wrap model data with persistence metadata
|
|
69
|
+
const data: SerializedChatData = {
|
|
70
|
+
version: CHAT_DATA_VERSION,
|
|
71
|
+
title: session.title,
|
|
72
|
+
pinnedAgentId: session.pinnedAgentId,
|
|
73
|
+
saveDate: session.saveDate,
|
|
74
|
+
model: modelData
|
|
75
|
+
};
|
|
76
|
+
this.logger.debug('Writing session to file', {
|
|
77
|
+
sessionId: session.model.id,
|
|
78
|
+
title: data.title,
|
|
79
|
+
filePath: sessionFile.toString(),
|
|
80
|
+
requestCount: modelData.requests.length,
|
|
81
|
+
responseCount: modelData.responses.length,
|
|
82
|
+
pinnedAgentId: data.pinnedAgentId,
|
|
83
|
+
version: data.version
|
|
84
|
+
});
|
|
85
|
+
await this.fileService.writeFile(
|
|
86
|
+
sessionFile,
|
|
87
|
+
BinaryBuffer.fromString(JSON.stringify(data, undefined, 2))
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Update index with metadata
|
|
92
|
+
await this.updateIndex(nonEmptySessions);
|
|
93
|
+
|
|
94
|
+
// Trim to max sessions
|
|
95
|
+
await this.trimSessions();
|
|
96
|
+
this.logger.debug('Finished storing sessions');
|
|
97
|
+
});
|
|
98
|
+
return this.storePromise;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private isChatModelWithMetadata(session: ChatModel | ChatModelWithMetadata): session is ChatModelWithMetadata {
|
|
102
|
+
return 'model' in session;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async readSession(sessionId: string): Promise<SerializedChatData | undefined> {
|
|
106
|
+
const root = await this.getStorageRoot();
|
|
107
|
+
const sessionFile = root.resolve(`${sessionId}.json`);
|
|
108
|
+
this.logger.debug('Reading session from file', { sessionId, filePath: sessionFile.toString() });
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const content = await this.fileService.readFile(sessionFile);
|
|
112
|
+
const parsedData = JSON.parse(content.value.toString());
|
|
113
|
+
const data = this.migrateData(parsedData);
|
|
114
|
+
this.logger.debug('Successfully read session', {
|
|
115
|
+
sessionId,
|
|
116
|
+
requestCount: data.model.requests.length,
|
|
117
|
+
responseCount: data.model.responses.length,
|
|
118
|
+
version: data.version
|
|
119
|
+
});
|
|
120
|
+
return data;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
this.logger.debug('Failed to read session', { sessionId, error: e });
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async deleteSession(sessionId: string): Promise<void> {
|
|
128
|
+
this.storePromise = this.storePromise.then(async () => {
|
|
129
|
+
const root = await this.getStorageRoot();
|
|
130
|
+
const sessionFile = root.resolve(`${sessionId}.json`);
|
|
131
|
+
this.logger.debug('Deleting session', { sessionId, filePath: sessionFile.toString() });
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await this.fileService.delete(sessionFile);
|
|
135
|
+
this.logger.debug('Session file deleted', { sessionId });
|
|
136
|
+
} catch (e) {
|
|
137
|
+
this.logger.debug('Failed to delete session file (may not exist)', { sessionId, error: e });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Update index
|
|
141
|
+
const index = await this.loadIndex();
|
|
142
|
+
delete index[sessionId];
|
|
143
|
+
await this.saveIndex(index);
|
|
144
|
+
this.logger.debug('Session removed from index', { sessionId });
|
|
145
|
+
});
|
|
146
|
+
return this.storePromise;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async clearAllSessions(): Promise<void> {
|
|
150
|
+
this.storePromise = this.storePromise.then(async () => {
|
|
151
|
+
const root = await this.getStorageRoot();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await this.fileService.delete(root, { recursive: true });
|
|
155
|
+
await this.fileService.createFolder(root);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// Ignore errors
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.indexCache = {};
|
|
161
|
+
await this.saveIndex({});
|
|
162
|
+
});
|
|
163
|
+
return this.storePromise;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async getSessionIndex(): Promise<ChatSessionIndex> {
|
|
167
|
+
const index = await this.loadIndex();
|
|
168
|
+
this.logger.debug('Retrieved session index', { sessionCount: Object.keys(index).length });
|
|
169
|
+
return index;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async setSessionTitle(sessionId: string, title: string): Promise<void> {
|
|
173
|
+
this.storePromise = this.storePromise.then(async () => {
|
|
174
|
+
const index = await this.loadIndex();
|
|
175
|
+
if (index[sessionId]) {
|
|
176
|
+
index[sessionId].title = title;
|
|
177
|
+
await this.saveIndex(index);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return this.storePromise;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected async getStorageRoot(): Promise<URI> {
|
|
184
|
+
if (this.storageRoot) {
|
|
185
|
+
return this.storageRoot;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const configDir = await this.envServer.getConfigDirUri();
|
|
189
|
+
this.storageRoot = new URI(configDir).resolve('chatSessions');
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await this.fileService.createFolder(this.storageRoot);
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// Folder may already exist
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return this.storageRoot;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected async updateIndex(sessions: ((ChatModelWithMetadata & { saveDate: number })[])): Promise<void> {
|
|
201
|
+
const index = await this.loadIndex();
|
|
202
|
+
|
|
203
|
+
for (const session of sessions) {
|
|
204
|
+
const data = session.model.toSerializable();
|
|
205
|
+
const { model, ...metadata } = session;
|
|
206
|
+
const previousData = index[model.id];
|
|
207
|
+
index[model.id] = {
|
|
208
|
+
...previousData,
|
|
209
|
+
sessionId: model.id,
|
|
210
|
+
location: data.location,
|
|
211
|
+
...metadata
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await this.saveIndex(index);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected async trimSessions(): Promise<void> {
|
|
219
|
+
const index = await this.loadIndex();
|
|
220
|
+
const sessions = Object.values(index);
|
|
221
|
+
|
|
222
|
+
if (sessions.length <= MAX_SESSIONS) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.logger.debug('Trimming sessions', { currentCount: sessions.length, maxSessions: MAX_SESSIONS });
|
|
227
|
+
|
|
228
|
+
// Sort by save date
|
|
229
|
+
sessions.sort((a, b) => a.saveDate - b.saveDate);
|
|
230
|
+
|
|
231
|
+
// Delete oldest sessions
|
|
232
|
+
const sessionsToDelete = sessions.slice(0, sessions.length - MAX_SESSIONS);
|
|
233
|
+
this.logger.debug('Deleting oldest sessions', { deleteCount: sessionsToDelete.length, sessionIds: sessionsToDelete.map(s => s.sessionId) });
|
|
234
|
+
for (const session of sessionsToDelete) {
|
|
235
|
+
await this.deleteSession(session.sessionId);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected async loadIndex(): Promise<ChatSessionIndex> {
|
|
240
|
+
if (this.indexCache) {
|
|
241
|
+
return this.indexCache;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const root = await this.getStorageRoot();
|
|
245
|
+
const indexFile = root.resolve(INDEX_FILE);
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const content = await this.fileService.readFile(indexFile);
|
|
249
|
+
const rawIndex = JSON.parse(content.value.toString());
|
|
250
|
+
|
|
251
|
+
// Validate and clean up index entries
|
|
252
|
+
const validatedIndex: ChatSessionIndex = {};
|
|
253
|
+
let hasInvalidEntries = false;
|
|
254
|
+
|
|
255
|
+
for (const [sessionId, metadata] of Object.entries(rawIndex)) {
|
|
256
|
+
// Check if entry has required fields and valid values
|
|
257
|
+
if (this.isValidMetadata(metadata)) {
|
|
258
|
+
validatedIndex[sessionId] = metadata as ChatSessionMetadata;
|
|
259
|
+
} else {
|
|
260
|
+
hasInvalidEntries = true;
|
|
261
|
+
this.logger.warn('Removing invalid session metadata from index', {
|
|
262
|
+
sessionId,
|
|
263
|
+
metadata
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// If we removed any entries, persist the cleaned index
|
|
269
|
+
if (hasInvalidEntries) {
|
|
270
|
+
this.logger.info('Index cleaned up, removing invalid entries');
|
|
271
|
+
await this.fileService.writeFile(
|
|
272
|
+
indexFile,
|
|
273
|
+
BinaryBuffer.fromString(JSON.stringify(validatedIndex, undefined, 2))
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.indexCache = validatedIndex;
|
|
278
|
+
return this.indexCache;
|
|
279
|
+
} catch (e) {
|
|
280
|
+
this.indexCache = {};
|
|
281
|
+
return this.indexCache;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
protected isValidMetadata(metadata: unknown): metadata is ChatSessionMetadata {
|
|
286
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const m = metadata as Record<string, unknown>;
|
|
291
|
+
|
|
292
|
+
// Check required fields exist and have correct types
|
|
293
|
+
return typeof m.sessionId === 'string' &&
|
|
294
|
+
typeof m.title === 'string' &&
|
|
295
|
+
typeof m.saveDate === 'number' &&
|
|
296
|
+
typeof m.location === 'string' &&
|
|
297
|
+
// Ensure saveDate is a valid timestamp
|
|
298
|
+
!isNaN(m.saveDate) &&
|
|
299
|
+
m.saveDate > 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
protected async saveIndex(index: ChatSessionIndex): Promise<void> {
|
|
303
|
+
this.indexCache = index;
|
|
304
|
+
const root = await this.getStorageRoot();
|
|
305
|
+
const indexFile = root.resolve(INDEX_FILE);
|
|
306
|
+
|
|
307
|
+
await this.fileService.writeFile(
|
|
308
|
+
indexFile,
|
|
309
|
+
BinaryBuffer.fromString(JSON.stringify(index, undefined, 2))
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
protected migrateData(data: unknown): SerializedChatData {
|
|
314
|
+
const parsed = data as SerializedChatData;
|
|
315
|
+
|
|
316
|
+
// Defensive check for unexpected future versions
|
|
317
|
+
if (parsed.version && parsed.version > CHAT_DATA_VERSION) {
|
|
318
|
+
this.logger.warn(
|
|
319
|
+
`Session data version ${parsed.version} is newer than supported ${CHAT_DATA_VERSION}. ` +
|
|
320
|
+
'Data may not load correctly.'
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return parsed;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2025 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ContributionProvider, MaybePromise, URI } from '@theia/core';
|
|
19
|
+
import { ChangeSetElement } from './change-set';
|
|
20
|
+
import { SerializableChangeSetElement } from './chat-model-serialization';
|
|
21
|
+
|
|
22
|
+
export const ChangeSetElementDeserializer = Symbol('ChangeSetElementDeserializer');
|
|
23
|
+
|
|
24
|
+
export interface ChangeSetElementDeserializer<T = unknown> {
|
|
25
|
+
readonly kind: string;
|
|
26
|
+
deserialize(serialized: SerializableChangeSetElement, context: ChangeSetDeserializationContext): ChangeSetElement | Promise<ChangeSetElement>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ChangeSetDeserializationContext {
|
|
30
|
+
chatSessionId: string;
|
|
31
|
+
requestId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ChangeSetElementDeserializerContribution {
|
|
35
|
+
registerDeserializers(registry: ChangeSetElementDeserializerRegistry): void;
|
|
36
|
+
}
|
|
37
|
+
export const ChangeSetElementDeserializerContribution = Symbol('ChangeSetElementDeserializerContribution');
|
|
38
|
+
export interface ChangeSetElementDeserializerRegistry {
|
|
39
|
+
register(deserializer: ChangeSetElementDeserializer): void;
|
|
40
|
+
deserialize(serialized: SerializableChangeSetElement, context: ChangeSetDeserializationContext): MaybePromise<ChangeSetElement>;
|
|
41
|
+
}
|
|
42
|
+
export const ChangeSetElementDeserializerRegistry = Symbol('ChangeSetElementDeserializerRegistry');
|
|
43
|
+
|
|
44
|
+
@injectable()
|
|
45
|
+
export class ChangeSetElementDeserializerRegistryImpl implements ChangeSetElementDeserializerRegistry {
|
|
46
|
+
protected deserializers = new Map<string, ChangeSetElementDeserializer>();
|
|
47
|
+
|
|
48
|
+
@inject(ContributionProvider) @named(ChangeSetElementDeserializerContribution)
|
|
49
|
+
protected readonly changeSetElementDeserializerContributions: ContributionProvider<ChangeSetElementDeserializerContribution>;
|
|
50
|
+
|
|
51
|
+
@postConstruct() init(): void {
|
|
52
|
+
for (const contribution of this.changeSetElementDeserializerContributions.getContributions()) {
|
|
53
|
+
contribution.registerDeserializers(this);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
register(deserializer: ChangeSetElementDeserializer): void {
|
|
58
|
+
this.deserializers.set(deserializer.kind, deserializer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
deserialize(serialized: SerializableChangeSetElement, context: ChangeSetDeserializationContext): MaybePromise<ChangeSetElement> {
|
|
62
|
+
const deserializer = this.deserializers.get(serialized.kind || 'generic');
|
|
63
|
+
if (!deserializer) {
|
|
64
|
+
return this.createFallbackElement(serialized);
|
|
65
|
+
}
|
|
66
|
+
return deserializer.deserialize(serialized, context);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private createFallbackElement(serialized: SerializableChangeSetElement): ChangeSetElement {
|
|
70
|
+
return {
|
|
71
|
+
uri: new URI(serialized.uri),
|
|
72
|
+
name: serialized.name,
|
|
73
|
+
icon: serialized.icon,
|
|
74
|
+
additionalInfo: serialized.additionalInfo,
|
|
75
|
+
state: serialized.state,
|
|
76
|
+
type: serialized.type,
|
|
77
|
+
data: serialized.data,
|
|
78
|
+
toSerializable: (): SerializableChangeSetElement => ({
|
|
79
|
+
kind: serialized.kind || 'generic',
|
|
80
|
+
uri: serialized.uri,
|
|
81
|
+
name: serialized.name,
|
|
82
|
+
icon: serialized.icon,
|
|
83
|
+
additionalInfo: serialized.additionalInfo,
|
|
84
|
+
state: serialized.state,
|
|
85
|
+
type: serialized.type,
|
|
86
|
+
data: serialized.data
|
|
87
|
+
})
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/common/change-set.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { ArrayUtils, Disposable, Emitter, Event, nls, URI } from '@theia/core';
|
|
18
|
+
import { SerializableChangeSetElement } from './chat-model-serialization';
|
|
18
19
|
|
|
19
20
|
export interface ChangeSetElement {
|
|
20
21
|
readonly uri: URI;
|
|
@@ -37,6 +38,13 @@ export interface ChangeSetElement {
|
|
|
37
38
|
apply?(): Promise<void>;
|
|
38
39
|
revert?(): Promise<void>;
|
|
39
40
|
dispose?(): void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Serializes this element to a format suitable for persistence.
|
|
44
|
+
* Each element type is responsible for serializing its own data.
|
|
45
|
+
* Optional - elements without this method will be excluded from serialization.
|
|
46
|
+
*/
|
|
47
|
+
toSerializable?(): SerializableChangeSetElement;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
export interface ChatUpdateChangeSetEvent {
|