@theia/ai-chat 1.66.0-next.67 → 1.66.0-next.80
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,372 @@
|
|
|
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 { expect } from 'chai';
|
|
18
|
+
import { Container } from '@theia/core/shared/inversify';
|
|
19
|
+
import { ChatServiceImpl } from './chat-service';
|
|
20
|
+
import { ChatSessionStore, ChatSessionIndex, ChatModelWithMetadata } from './chat-session-store';
|
|
21
|
+
import { ChatAgentService } from './chat-agent-service';
|
|
22
|
+
import { ChatRequestParser } from './chat-request-parser';
|
|
23
|
+
import { AIVariableService } from '@theia/ai-core';
|
|
24
|
+
import { ILogger } from '@theia/core';
|
|
25
|
+
import { ChatAgentLocation } from './chat-agents';
|
|
26
|
+
import { ChatContentDeserializerRegistry, ChatContentDeserializerRegistryImpl, DefaultChatContentDeserializerContribution } from './chat-content-deserializer';
|
|
27
|
+
import { ChangeSetElementDeserializerRegistry, ChangeSetElementDeserializerRegistryImpl } from './change-set-element-deserializer';
|
|
28
|
+
import { ChatModel } from './chat-model';
|
|
29
|
+
import { SerializedChatData } from './chat-model-serialization';
|
|
30
|
+
import { ParsedChatRequest } from './parsed-chat-request';
|
|
31
|
+
|
|
32
|
+
describe('Chat Auto-Save Mechanism', () => {
|
|
33
|
+
let chatService: ChatServiceImpl;
|
|
34
|
+
let sessionStore: MockChatSessionStore;
|
|
35
|
+
let container: Container;
|
|
36
|
+
|
|
37
|
+
class MockChatSessionStore implements ChatSessionStore {
|
|
38
|
+
public saveCount = 0;
|
|
39
|
+
public savedSessions: Array<ChatModel | ChatModelWithMetadata> = [];
|
|
40
|
+
public lastSaveTimes: Map<string, number> = new Map();
|
|
41
|
+
|
|
42
|
+
async storeSessions(...sessions: Array<ChatModel | ChatModelWithMetadata>): Promise<void> {
|
|
43
|
+
this.saveCount++;
|
|
44
|
+
this.savedSessions = sessions;
|
|
45
|
+
// Track save times per session
|
|
46
|
+
sessions.forEach(session => {
|
|
47
|
+
const id = 'model' in session ? session.model.id : session.id;
|
|
48
|
+
this.lastSaveTimes.set(id, Date.now());
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async readSession(sessionId: string): Promise<SerializedChatData | undefined> {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async deleteSession(sessionId: string): Promise<void> {
|
|
57
|
+
// No-op for mock
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async clearAllSessions(): Promise<void> {
|
|
61
|
+
this.savedSessions = [];
|
|
62
|
+
this.saveCount = 0;
|
|
63
|
+
this.lastSaveTimes.clear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getSessionIndex(): Promise<ChatSessionIndex> {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async setSessionTitle(sessionId: string, title: string): Promise<void> {
|
|
71
|
+
// No-op for mock
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
reset(): void {
|
|
75
|
+
this.saveCount = 0;
|
|
76
|
+
this.savedSessions = [];
|
|
77
|
+
this.lastSaveTimes.clear();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class MockChatAgentService {
|
|
82
|
+
private testAgent = {
|
|
83
|
+
id: 'test-agent',
|
|
84
|
+
name: 'Test Agent',
|
|
85
|
+
invoke: () => Promise.resolve()
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
getAgent(): typeof this.testAgent {
|
|
89
|
+
return this.testAgent;
|
|
90
|
+
}
|
|
91
|
+
getAgents(): typeof this.testAgent[] {
|
|
92
|
+
return [this.testAgent];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class MockChatRequestParser {
|
|
97
|
+
parseChatRequest(): Promise<ParsedChatRequest> {
|
|
98
|
+
return Promise.resolve({
|
|
99
|
+
request: { text: 'test' },
|
|
100
|
+
parts: [{
|
|
101
|
+
kind: 'text' as const,
|
|
102
|
+
text: 'test',
|
|
103
|
+
promptText: 'test',
|
|
104
|
+
range: { start: 0, endExclusive: 4 }
|
|
105
|
+
}],
|
|
106
|
+
toolRequests: new Map(),
|
|
107
|
+
variables: []
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class MockAIVariableService {
|
|
113
|
+
resolveVariables(): Promise<unknown[]> {
|
|
114
|
+
return Promise.resolve([]);
|
|
115
|
+
}
|
|
116
|
+
resolveVariable(): Promise<undefined> {
|
|
117
|
+
return Promise.resolve(undefined);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class MockLogger {
|
|
122
|
+
error(): void { }
|
|
123
|
+
warn(): void { }
|
|
124
|
+
info(): void { }
|
|
125
|
+
debug(): void { }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
container = new Container();
|
|
130
|
+
sessionStore = new MockChatSessionStore();
|
|
131
|
+
|
|
132
|
+
container.bind(ChatSessionStore).toConstantValue(sessionStore);
|
|
133
|
+
container.bind(ChatAgentService).toConstantValue(new MockChatAgentService() as unknown as ChatAgentService);
|
|
134
|
+
container.bind(ChatRequestParser).toConstantValue(new MockChatRequestParser() as unknown as ChatRequestParser);
|
|
135
|
+
container.bind(AIVariableService).toConstantValue(new MockAIVariableService() as unknown as AIVariableService);
|
|
136
|
+
container.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
|
|
137
|
+
|
|
138
|
+
// Bind deserializer registries
|
|
139
|
+
const contentRegistry = new ChatContentDeserializerRegistryImpl();
|
|
140
|
+
new DefaultChatContentDeserializerContribution().registerDeserializers(contentRegistry);
|
|
141
|
+
container.bind(ChatContentDeserializerRegistry).toConstantValue(contentRegistry);
|
|
142
|
+
container.bind(ChangeSetElementDeserializerRegistry).toConstantValue(new ChangeSetElementDeserializerRegistryImpl());
|
|
143
|
+
|
|
144
|
+
container.bind(ChatServiceImpl).toSelf().inSingletonScope();
|
|
145
|
+
|
|
146
|
+
chatService = container.get(ChatServiceImpl);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Auto-save on response completion', () => {
|
|
150
|
+
it('should auto-save when response is complete', async () => {
|
|
151
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
152
|
+
const initialSaveCount = sessionStore.saveCount;
|
|
153
|
+
|
|
154
|
+
// Send a request
|
|
155
|
+
const invocation = await chatService.sendRequest(session.id, { text: 'Test request' });
|
|
156
|
+
const responseModel = await invocation!.responseCreated;
|
|
157
|
+
|
|
158
|
+
// Complete the response
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
(responseModel as any).complete();
|
|
161
|
+
|
|
162
|
+
// Wait for auto-save to complete (debounce is 500ms + execution time)
|
|
163
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
164
|
+
|
|
165
|
+
// Verify session was auto-saved
|
|
166
|
+
expect(sessionStore.saveCount).to.be.greaterThan(initialSaveCount);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should auto-save when response has error', async () => {
|
|
170
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
171
|
+
const initialSaveCount = sessionStore.saveCount;
|
|
172
|
+
|
|
173
|
+
// Send a request that will error
|
|
174
|
+
const invocation = await chatService.sendRequest(session.id, { text: 'Test request' });
|
|
175
|
+
const responseModel = await invocation!.responseCreated;
|
|
176
|
+
|
|
177
|
+
// Simulate error
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
|
+
(responseModel as any).error(new Error('Test error'));
|
|
180
|
+
|
|
181
|
+
// Wait for auto-save to complete (debounce is 500ms + execution time)
|
|
182
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
183
|
+
|
|
184
|
+
// Verify session was auto-saved even on error
|
|
185
|
+
expect(sessionStore.saveCount).to.be.greaterThan(initialSaveCount);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Auto-save on changeset updates', () => {
|
|
190
|
+
it('should auto-save when changeset elements are updated', async () => {
|
|
191
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
192
|
+
// Add a request so the session is not empty
|
|
193
|
+
await chatService.sendRequest(session.id, { text: 'Test request' });
|
|
194
|
+
sessionStore.reset();
|
|
195
|
+
|
|
196
|
+
// Trigger changeset update event via model's internal emitter
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
+
(session.model as any)._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: [] });
|
|
199
|
+
|
|
200
|
+
// Wait for auto-save to complete (debounce is 500ms + execution time)
|
|
201
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
202
|
+
|
|
203
|
+
// Verify session was auto-saved
|
|
204
|
+
expect(sessionStore.saveCount).to.be.greaterThan(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should not auto-save on non-changeset events', async () => {
|
|
208
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
209
|
+
sessionStore.reset();
|
|
210
|
+
|
|
211
|
+
// Trigger other kind of event (like 'addRequest')
|
|
212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
|
+
(session.model as any)._onDidChangeEmitter.fire({ kind: 'addRequest' });
|
|
214
|
+
|
|
215
|
+
// Wait to ensure no save happens
|
|
216
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
217
|
+
|
|
218
|
+
// Verify no auto-save occurred
|
|
219
|
+
expect(sessionStore.saveCount).to.equal(0);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('Auto-save for all sessions', () => {
|
|
224
|
+
it('should save all non-empty sessions', async () => {
|
|
225
|
+
const session1 = chatService.createSession(ChatAgentLocation.Panel);
|
|
226
|
+
const session2 = chatService.createSession(ChatAgentLocation.Panel);
|
|
227
|
+
|
|
228
|
+
sessionStore.reset();
|
|
229
|
+
|
|
230
|
+
// Send requests to both sessions
|
|
231
|
+
const invocation1 = await chatService.sendRequest(session1.id, { text: 'Request 1' });
|
|
232
|
+
const invocation2 = await chatService.sendRequest(session2.id, { text: 'Request 2' });
|
|
233
|
+
|
|
234
|
+
// Complete both responses
|
|
235
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
236
|
+
(await invocation1!.responseCreated as any).complete();
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
+
(await invocation2!.responseCreated as any).complete();
|
|
239
|
+
|
|
240
|
+
// Wait for auto-save to complete (debounce is 500ms + execution time)
|
|
241
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
242
|
+
|
|
243
|
+
// Verify both sessions were saved (check lastSaveTimes since sessions are saved individually)
|
|
244
|
+
expect(sessionStore.saveCount).to.be.greaterThan(0);
|
|
245
|
+
expect(sessionStore.lastSaveTimes.has(session1.id)).to.be.true;
|
|
246
|
+
expect(sessionStore.lastSaveTimes.has(session2.id)).to.be.true;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should not save empty sessions', async () => {
|
|
250
|
+
// Create session without any requests
|
|
251
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
252
|
+
sessionStore.reset();
|
|
253
|
+
|
|
254
|
+
// Manually trigger save
|
|
255
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
256
|
+
await (chatService as any).saveSession(session.id);
|
|
257
|
+
|
|
258
|
+
// Verify empty session was not saved
|
|
259
|
+
const savedSessionIds = sessionStore.savedSessions.map(s => 'model' in s ? s.model.id : s.id);
|
|
260
|
+
expect(savedSessionIds).to.not.include(session.id);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('Auto-save without session store', () => {
|
|
265
|
+
it('should handle auto-save gracefully when session store unavailable', async () => {
|
|
266
|
+
// Create service without session store
|
|
267
|
+
const containerWithoutStore = new Container();
|
|
268
|
+
containerWithoutStore.bind(ChatAgentService).toConstantValue(new MockChatAgentService() as unknown as ChatAgentService);
|
|
269
|
+
containerWithoutStore.bind(ChatRequestParser).toConstantValue(new MockChatRequestParser() as unknown as ChatRequestParser);
|
|
270
|
+
containerWithoutStore.bind(AIVariableService).toConstantValue(new MockAIVariableService() as unknown as AIVariableService);
|
|
271
|
+
containerWithoutStore.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
|
|
272
|
+
|
|
273
|
+
// Bind deserializer registries
|
|
274
|
+
const contentRegistry = new ChatContentDeserializerRegistryImpl();
|
|
275
|
+
new DefaultChatContentDeserializerContribution().registerDeserializers(contentRegistry);
|
|
276
|
+
containerWithoutStore.bind(ChatContentDeserializerRegistry).toConstantValue(contentRegistry);
|
|
277
|
+
containerWithoutStore.bind(ChangeSetElementDeserializerRegistry).toConstantValue(new ChangeSetElementDeserializerRegistryImpl());
|
|
278
|
+
|
|
279
|
+
containerWithoutStore.bind(ChatServiceImpl).toSelf().inSingletonScope();
|
|
280
|
+
|
|
281
|
+
const serviceWithoutStore = containerWithoutStore.get(ChatServiceImpl);
|
|
282
|
+
|
|
283
|
+
// Create session and send request
|
|
284
|
+
const session = serviceWithoutStore.createSession(ChatAgentLocation.Panel);
|
|
285
|
+
const invocation = await serviceWithoutStore.sendRequest(session.id, { text: 'Test' });
|
|
286
|
+
|
|
287
|
+
// Complete response - should not throw even without session store
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
289
|
+
(await invocation!.responseCreated as any).complete();
|
|
290
|
+
|
|
291
|
+
// Wait to ensure no errors
|
|
292
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
293
|
+
|
|
294
|
+
// No assertion needed - we're just verifying no exception is thrown
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('Auto-save setup for restored sessions', () => {
|
|
299
|
+
it('should set up auto-save for restored sessions', async () => {
|
|
300
|
+
// Create and save a session
|
|
301
|
+
const session1 = chatService.createSession(ChatAgentLocation.Panel);
|
|
302
|
+
await chatService.sendRequest(session1.id, { text: 'Test' });
|
|
303
|
+
|
|
304
|
+
const serialized = session1.model.toSerializable();
|
|
305
|
+
|
|
306
|
+
// Create new service instance
|
|
307
|
+
const newContainer = new Container();
|
|
308
|
+
const newSessionStore = new MockChatSessionStore();
|
|
309
|
+
|
|
310
|
+
newContainer.bind(ChatSessionStore).toConstantValue(newSessionStore);
|
|
311
|
+
newContainer.bind(ChatAgentService).toConstantValue(new MockChatAgentService() as unknown as ChatAgentService);
|
|
312
|
+
newContainer.bind(ChatRequestParser).toConstantValue(new MockChatRequestParser() as unknown as ChatRequestParser);
|
|
313
|
+
newContainer.bind(AIVariableService).toConstantValue(new MockAIVariableService() as unknown as AIVariableService);
|
|
314
|
+
newContainer.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
|
|
315
|
+
|
|
316
|
+
// Bind deserializer registries
|
|
317
|
+
const newContentRegistry = new ChatContentDeserializerRegistryImpl();
|
|
318
|
+
new DefaultChatContentDeserializerContribution().registerDeserializers(newContentRegistry);
|
|
319
|
+
newContainer.bind(ChatContentDeserializerRegistry).toConstantValue(newContentRegistry);
|
|
320
|
+
newContainer.bind(ChangeSetElementDeserializerRegistry).toConstantValue(new ChangeSetElementDeserializerRegistryImpl());
|
|
321
|
+
|
|
322
|
+
newContainer.bind(ChatServiceImpl).toSelf().inSingletonScope();
|
|
323
|
+
|
|
324
|
+
const newChatService = newContainer.get(ChatServiceImpl);
|
|
325
|
+
|
|
326
|
+
// Mock readSession to return the serialized data
|
|
327
|
+
newSessionStore.readSession = async (id: string) => ({
|
|
328
|
+
version: 1,
|
|
329
|
+
model: serialized,
|
|
330
|
+
pinnedAgentId: undefined,
|
|
331
|
+
saveDate: Date.now()
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Restore the session
|
|
335
|
+
const restoredSession = await newChatService.getOrRestoreSession(serialized.sessionId);
|
|
336
|
+
expect(restoredSession).to.not.be.undefined;
|
|
337
|
+
|
|
338
|
+
newSessionStore.reset();
|
|
339
|
+
|
|
340
|
+
// Trigger changeset update on restored session
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
342
|
+
(restoredSession!.model as any)._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: [] });
|
|
343
|
+
|
|
344
|
+
// Wait for auto-save (debounce is 500ms + execution time)
|
|
345
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
346
|
+
|
|
347
|
+
// Verify auto-save was set up and triggered
|
|
348
|
+
expect(newSessionStore.saveCount).to.be.greaterThan(0);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('Multiple auto-save triggers', () => {
|
|
353
|
+
it('should handle multiple rapid auto-save triggers', async () => {
|
|
354
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
355
|
+
// Add a request so the session is not empty
|
|
356
|
+
await chatService.sendRequest(session.id, { text: 'Test request' });
|
|
357
|
+
sessionStore.reset();
|
|
358
|
+
|
|
359
|
+
// Trigger multiple changeset updates rapidly
|
|
360
|
+
for (let i = 0; i < 5; i++) {
|
|
361
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
362
|
+
(session.model as any)._onDidChangeEmitter.fire({ kind: 'updateChangeSet', elements: [] });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Wait for all saves to complete (debounce is 500ms + execution time)
|
|
366
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
367
|
+
|
|
368
|
+
// Verify saves were triggered (implementation batches them)
|
|
369
|
+
expect(sessionStore.saveCount).to.be.greaterThan(0);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|