@theia/ai-chat 1.66.0-next.44 → 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.
Files changed (137) hide show
  1. package/lib/browser/agent-delegation-tool.d.ts.map +1 -1
  2. package/lib/browser/agent-delegation-tool.js +4 -2
  3. package/lib/browser/agent-delegation-tool.js.map +1 -1
  4. package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
  5. package/lib/browser/ai-chat-frontend-module.js +15 -0
  6. package/lib/browser/ai-chat-frontend-module.js.map +1 -1
  7. package/lib/browser/change-set-file-element-deserializer.d.ts +7 -0
  8. package/lib/browser/change-set-file-element-deserializer.d.ts.map +1 -0
  9. package/lib/browser/change-set-file-element-deserializer.js +61 -0
  10. package/lib/browser/change-set-file-element-deserializer.js.map +1 -0
  11. package/lib/browser/change-set-file-element.d.ts +3 -1
  12. package/lib/browser/change-set-file-element.d.ts.map +1 -1
  13. package/lib/browser/change-set-file-element.js +23 -2
  14. package/lib/browser/change-set-file-element.js.map +1 -1
  15. package/lib/browser/change-set-file-service.d.ts.map +1 -1
  16. package/lib/browser/change-set-file-service.js +2 -2
  17. package/lib/browser/change-set-file-service.js.map +1 -1
  18. package/lib/browser/chat-session-store-impl.d.ts +36 -0
  19. package/lib/browser/chat-session-store-impl.d.ts.map +1 -0
  20. package/lib/browser/chat-session-store-impl.js +287 -0
  21. package/lib/browser/chat-session-store-impl.js.map +1 -0
  22. package/lib/browser/file-chat-variable-contribution.d.ts.map +1 -1
  23. package/lib/browser/file-chat-variable-contribution.js +1 -1
  24. package/lib/browser/file-chat-variable-contribution.js.map +1 -1
  25. package/lib/browser/image-context-variable-contribution.js +1 -1
  26. package/lib/browser/image-context-variable-contribution.js.map +1 -1
  27. package/lib/browser/task-context-service.d.ts.map +1 -1
  28. package/lib/browser/task-context-service.js +8 -2
  29. package/lib/browser/task-context-service.js.map +1 -1
  30. package/lib/browser/task-context-variable.d.ts.map +1 -1
  31. package/lib/browser/task-context-variable.js +7 -3
  32. package/lib/browser/task-context-variable.js.map +1 -1
  33. package/lib/common/change-set-element-deserializer.d.ts +30 -0
  34. package/lib/common/change-set-element-deserializer.d.ts.map +1 -0
  35. package/lib/common/change-set-element-deserializer.js +81 -0
  36. package/lib/common/change-set-element-deserializer.js.map +1 -0
  37. package/lib/common/change-set.d.ts +7 -0
  38. package/lib/common/change-set.d.ts.map +1 -1
  39. package/lib/common/change-set.js +1 -1
  40. package/lib/common/change-set.js.map +1 -1
  41. package/lib/common/chat-agents-variable-contribution.d.ts.map +1 -1
  42. package/lib/common/chat-agents-variable-contribution.js +17 -1
  43. package/lib/common/chat-agents-variable-contribution.js.map +1 -1
  44. package/lib/common/chat-agents.d.ts.map +1 -1
  45. package/lib/common/chat-agents.js +3 -3
  46. package/lib/common/chat-agents.js.map +1 -1
  47. package/lib/common/chat-auto-save.spec.d.ts +2 -0
  48. package/lib/common/chat-auto-save.spec.d.ts.map +1 -0
  49. package/lib/common/chat-auto-save.spec.js +304 -0
  50. package/lib/common/chat-auto-save.spec.js.map +1 -0
  51. package/lib/common/chat-content-deserializer.d.ts +161 -0
  52. package/lib/common/chat-content-deserializer.d.ts.map +1 -0
  53. package/lib/common/chat-content-deserializer.js +166 -0
  54. package/lib/common/chat-content-deserializer.js.map +1 -0
  55. package/lib/common/chat-content-deserializer.spec.d.ts +2 -0
  56. package/lib/common/chat-content-deserializer.spec.d.ts.map +1 -0
  57. package/lib/common/chat-content-deserializer.spec.js +307 -0
  58. package/lib/common/chat-content-deserializer.spec.js.map +1 -0
  59. package/lib/common/chat-model-serialization.d.ts +110 -0
  60. package/lib/common/chat-model-serialization.d.ts.map +1 -0
  61. package/lib/common/chat-model-serialization.js +20 -0
  62. package/lib/common/chat-model-serialization.js.map +1 -0
  63. package/lib/common/chat-model-serialization.spec.d.ts +2 -0
  64. package/lib/common/chat-model-serialization.spec.d.ts.map +1 -0
  65. package/lib/common/chat-model-serialization.spec.js +278 -0
  66. package/lib/common/chat-model-serialization.spec.js.map +1 -0
  67. package/lib/common/chat-model.d.ts +163 -14
  68. package/lib/common/chat-model.d.ts.map +1 -1
  69. package/lib/common/chat-model.js +444 -36
  70. package/lib/common/chat-model.js.map +1 -1
  71. package/lib/common/chat-request-parser.d.ts +8 -1
  72. package/lib/common/chat-request-parser.d.ts.map +1 -1
  73. package/lib/common/chat-request-parser.js +29 -1
  74. package/lib/common/chat-request-parser.js.map +1 -1
  75. package/lib/common/chat-request-parser.spec.js +51 -0
  76. package/lib/common/chat-request-parser.spec.js.map +1 -1
  77. package/lib/common/chat-service-deletion.spec.d.ts +2 -0
  78. package/lib/common/chat-service-deletion.spec.d.ts.map +1 -0
  79. package/lib/common/chat-service-deletion.spec.js +221 -0
  80. package/lib/common/chat-service-deletion.spec.js.map +1 -0
  81. package/lib/common/chat-service.d.ts +30 -2
  82. package/lib/common/chat-service.d.ts.map +1 -1
  83. package/lib/common/chat-service.js +182 -10
  84. package/lib/common/chat-service.js.map +1 -1
  85. package/lib/common/chat-session-naming-service.d.ts.map +1 -1
  86. package/lib/common/chat-session-naming-service.js +11 -3
  87. package/lib/common/chat-session-naming-service.js.map +1 -1
  88. package/lib/common/chat-session-store.d.ts +43 -0
  89. package/lib/common/chat-session-store.d.ts.map +1 -0
  90. package/lib/common/chat-session-store.js +20 -0
  91. package/lib/common/chat-session-store.js.map +1 -0
  92. package/lib/common/chat-session-summary-agent-prompt.js +1 -1
  93. package/lib/common/chat-session-summary-agent-prompt.js.map +1 -1
  94. package/lib/common/chat-session-summary-agent.d.ts.map +1 -1
  95. package/lib/common/chat-session-summary-agent.js +2 -1
  96. package/lib/common/chat-session-summary-agent.js.map +1 -1
  97. package/lib/common/chat-tool-preferences.js +1 -1
  98. package/lib/common/chat-tool-preferences.js.map +1 -1
  99. package/lib/common/image-context-variable.d.ts.map +1 -1
  100. package/lib/common/image-context-variable.js +21 -6
  101. package/lib/common/image-context-variable.js.map +1 -1
  102. package/lib/common/index.d.ts +3 -0
  103. package/lib/common/index.d.ts.map +1 -1
  104. package/lib/common/index.js +3 -0
  105. package/lib/common/index.js.map +1 -1
  106. package/package.json +9 -9
  107. package/src/browser/agent-delegation-tool.ts +4 -2
  108. package/src/browser/ai-chat-frontend-module.ts +27 -0
  109. package/src/browser/change-set-file-element-deserializer.ts +62 -0
  110. package/src/browser/change-set-file-element.ts +29 -4
  111. package/src/browser/change-set-file-service.ts +3 -3
  112. package/src/browser/chat-session-store-impl.ts +326 -0
  113. package/src/browser/file-chat-variable-contribution.ts +2 -2
  114. package/src/browser/image-context-variable-contribution.ts +1 -1
  115. package/src/browser/task-context-service.ts +9 -3
  116. package/src/browser/task-context-variable.ts +8 -3
  117. package/src/common/change-set-element-deserializer.ts +90 -0
  118. package/src/common/change-set.ts +10 -2
  119. package/src/common/chat-agents-variable-contribution.ts +2 -2
  120. package/src/common/chat-agents.ts +4 -4
  121. package/src/common/chat-auto-save.spec.ts +372 -0
  122. package/src/common/chat-content-deserializer.spec.ts +375 -0
  123. package/src/common/chat-content-deserializer.ts +327 -0
  124. package/src/common/chat-model-serialization.spec.ts +343 -0
  125. package/src/common/chat-model-serialization.ts +133 -0
  126. package/src/common/chat-model.ts +644 -41
  127. package/src/common/chat-request-parser.spec.ts +61 -0
  128. package/src/common/chat-request-parser.ts +40 -1
  129. package/src/common/chat-service-deletion.spec.ts +269 -0
  130. package/src/common/chat-service.ts +227 -10
  131. package/src/common/chat-session-naming-service.ts +12 -4
  132. package/src/common/chat-session-store.ts +63 -0
  133. package/src/common/chat-session-summary-agent-prompt.ts +1 -1
  134. package/src/common/chat-session-summary-agent.ts +2 -1
  135. package/src/common/chat-tool-preferences.ts +1 -2
  136. package/src/common/image-context-variable.ts +21 -6
  137. 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
+ }
@@ -17,7 +17,7 @@
17
17
  import { AIVariableContext, AIVariableResolutionRequest, PromptText } from '@theia/ai-core';
18
18
  import { AIVariableCompletionContext, AIVariableDropResult, FrontendVariableContribution, FrontendVariableService } from '@theia/ai-core/lib/browser';
19
19
  import { FILE_VARIABLE } from '@theia/ai-core/lib/browser/file-variable-contribution';
20
- import { CancellationToken, ILogger, QuickInputService, URI } from '@theia/core';
20
+ import { CancellationToken, ILogger, nls, QuickInputService, URI } from '@theia/core';
21
21
  import { inject, injectable } from '@theia/core/shared/inversify';
22
22
  import * as monaco from '@theia/monaco-editor-core';
23
23
  import { FileQuickPickItem, QuickFileSelectService } from '@theia/file-search/lib/browser/quick-file-select-service';
@@ -76,7 +76,7 @@ export class FileChatVariableContribution implements FrontendVariableContributio
76
76
 
77
77
  protected async imageArgumentPicker(): Promise<string | undefined> {
78
78
  const quickPick = this.quickInputService.createQuickPick();
79
- quickPick.title = 'Select an image file';
79
+ quickPick.title = nls.localize('theia/ai/chat/selectImageFile', 'Select an image file');
80
80
 
81
81
  // Get all files and filter only image files
82
82
  const allPicks = await this.quickFileSelectService.getPicks();
@@ -148,6 +148,6 @@ export class ImageContextVariableContribution implements AIVariableContribution,
148
148
 
149
149
  getDetails(element: ImageContextVariableRequest): string | undefined {
150
150
  const path = ImageContextVariable.parseArg(element.arg).wsRelativePath;
151
- return path ? this.labelProvider.getDetails(new URI(path)) : '[pasted]';
151
+ return path ? this.labelProvider.getDetails(new URI(path)) : undefined;
152
152
  }
153
153
  }
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
- import { MaybePromise, ProgressService, URI, generateUuid, Event, EOL } from '@theia/core';
18
+ import { MaybePromise, ProgressService, URI, generateUuid, Event, EOL, nls } from '@theia/core';
19
19
  import { ChatAgent, ChatAgentLocation, ChatService, ChatSession, MutableChatModel, MutableChatRequestModel, ParsedChatRequestTextPart } from '../common';
20
20
  import { PreferenceService } from '@theia/core/lib/common';
21
21
  import { ChatSessionSummaryAgent } from '../common/chat-session-summary-agent';
@@ -90,7 +90,10 @@ export class TaskContextService {
90
90
  if (existing && !override) { return existing.id; }
91
91
  const summaryId = generateUuid();
92
92
  const summaryDeferred = new Deferred<Summary>();
93
- const progress = await this.progressService.showProgress({ text: `Summarize: ${session.title || session.id}`, options: { location: 'ai-chat' } });
93
+ const progress = await this.progressService.showProgress({
94
+ text: nls.localize('theia/ai/chat/taskContextService/summarizeProgressMessage', 'Summarize: {0}', session.title || session.id),
95
+ options: { location: 'ai-chat' }
96
+ });
94
97
  this.pendingSummaries.set(session.id, summaryDeferred.promise);
95
98
  try {
96
99
  const prompt = await this.getSystemPrompt(session, promptId);
@@ -127,7 +130,10 @@ export class TaskContextService {
127
130
  return this.summarize(session, promptId, agent, override);
128
131
  }
129
132
 
130
- const progress = await this.progressService.showProgress({ text: `Updating: ${session.title || session.id}`, options: { location: 'ai-chat' } });
133
+ const progress = await this.progressService.showProgress({
134
+ text: nls.localize('theia/ai/chat/taskContextService/updatingProgressMessage', 'Updating: {0}', session.title || session.id),
135
+ options: { location: 'ai-chat' }
136
+ });
131
137
  try {
132
138
  const prompt = await this.getSystemPrompt(session, promptId);
133
139
  if (!prompt) {
@@ -15,14 +15,19 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { AIVariable } from '@theia/ai-core';
18
+ import { nls } from '@theia/core';
18
19
  import { codiconArray } from '@theia/core/lib/browser';
19
20
 
20
21
  export const TASK_CONTEXT_VARIABLE: AIVariable = {
21
22
  id: 'taskContext',
22
- description: 'Provides context information for a task, e.g. the plan for completing a task or a summary of a previous sessions',
23
+ description: nls.localize('theia/chat/taskContextVariable/description',
24
+ 'Provides context information for a task, e.g. the plan for completing a task or a summary of a previous sessions'),
23
25
  name: 'taskContext',
24
- label: 'Task Context',
26
+ label: nls.localize('theia/chat/taskContextVariable/label', 'Task Context'),
25
27
  iconClasses: codiconArray('clippy'),
26
28
  isContextVariable: true,
27
- args: [{ name: 'context-id', description: 'The ID of the task context to retrieve, or a chat session to summarize.' }]
29
+ args: [{
30
+ name: 'context-id',
31
+ description: nls.localize('theia/chat/taskContextVariable/args/contextId/description', 'The ID of the task context to retrieve, or a chat session to summarize.')
32
+ }]
28
33
  };
@@ -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
+ }
@@ -14,7 +14,8 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { ArrayUtils, Disposable, Emitter, Event, URI } from '@theia/core';
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 {
@@ -95,7 +103,7 @@ export class ChangeSetImpl implements ChangeSet {
95
103
 
96
104
  protected hasBeenSet = false;
97
105
  protected _elements = new Map<string, ChangeSetElement | undefined>();
98
- protected _title = 'Suggested Changes';
106
+ protected _title = nls.localize('theia/ai/chat/changeSetDefaultTitle', 'Suggested Changes');
99
107
  get title(): string {
100
108
  return this._title;
101
109
  }
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import { MaybePromise } from '@theia/core';
16
+ import { MaybePromise, nls } from '@theia/core';
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
18
  import {
19
19
  AIVariable,
@@ -29,7 +29,7 @@ import { ChatAgentService } from './chat-agent-service';
29
29
  export const CHAT_AGENTS_VARIABLE: AIVariable = {
30
30
  id: 'chatAgents',
31
31
  name: 'chatAgents',
32
- description: 'Returns the list of chat agents available in the system'
32
+ description: nls.localize('theia/ai/chat/chatAgentsVariable/description', 'Returns the list of chat agents available in the system')
33
33
  };
34
34
 
35
35
  export interface ChatAgentDescriptor {
@@ -49,7 +49,7 @@ import {
49
49
  LanguageModelRegistry,
50
50
  LanguageModelStreamResponsePart
51
51
  } from '@theia/ai-core/lib/common';
52
- import { ContributionProvider, ILogger, isArray } from '@theia/core';
52
+ import { ContributionProvider, ILogger, isArray, nls } from '@theia/core';
53
53
  import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
54
54
  import { ChatAgentService } from './chat-agent-service';
55
55
  import {
@@ -155,7 +155,7 @@ export abstract class AbstractChatAgent implements ChatAgent {
155
155
  readonly abstract languageModelRequirements: LanguageModelRequirement[];
156
156
  iconClass: string = 'codicon codicon-copilot';
157
157
  locations: ChatAgentLocation[] = ChatAgentLocation.ALL;
158
- tags: string[] = ['Chat'];
158
+ tags: string[] = [nls.localizeByDefault('Chat')];
159
159
  description: string = '';
160
160
  variables: string[] = [];
161
161
  prompts: PromptVariantSet[] = [];
@@ -180,7 +180,7 @@ export abstract class AbstractChatAgent implements ChatAgent {
180
180
  try {
181
181
  const languageModel = await this.getLanguageModel(this.defaultLanguageModelPurpose);
182
182
  if (!languageModel) {
183
- throw new Error('Couldn\'t find a matching language model. Please check your setup!');
183
+ throw new Error(nls.localize('theia/ai/chat/couldNotFindMatchingLM', 'Couldn\'t find a matching language model. Please check your setup!'));
184
184
  }
185
185
  const systemMessageDescription = await this.getSystemMessageDescription({ model: request.session, request } satisfies ChatSessionContext);
186
186
  const messages = await this.getMessages(request.session);
@@ -237,7 +237,7 @@ export abstract class AbstractChatAgent implements ChatAgent {
237
237
  protected async selectLanguageModel(selector: LanguageModelRequirement): Promise<LanguageModel> {
238
238
  const languageModel = await this.languageModelRegistry.selectLanguageModel({ agent: this.id, ...selector });
239
239
  if (!languageModel) {
240
- throw new Error(`Couldn\'t find a ready language model for agent ${this.id}. Please check your setup!`);
240
+ throw new Error(nls.localize('theia/ai/chat/couldNotFindReadyLMforAgent', 'Couldn\'t find a ready language model for agent {0}. Please check your setup!', this.id));
241
241
  }
242
242
  return languageModel;
243
243
  }