@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.
- 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 +3 -1
- package/lib/browser/change-set-file-element.d.ts.map +1 -1
- package/lib/browser/change-set-file-element.js +23 -2
- package/lib/browser/change-set-file-element.js.map +1 -1
- package/lib/browser/change-set-file-service.d.ts.map +1 -1
- package/lib/browser/change-set-file-service.js +2 -2
- package/lib/browser/change-set-file-service.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/browser/file-chat-variable-contribution.d.ts.map +1 -1
- package/lib/browser/file-chat-variable-contribution.js +1 -1
- package/lib/browser/file-chat-variable-contribution.js.map +1 -1
- package/lib/browser/image-context-variable-contribution.js +1 -1
- package/lib/browser/image-context-variable-contribution.js.map +1 -1
- package/lib/browser/task-context-service.d.ts.map +1 -1
- package/lib/browser/task-context-service.js +8 -2
- package/lib/browser/task-context-service.js.map +1 -1
- package/lib/browser/task-context-variable.d.ts.map +1 -1
- package/lib/browser/task-context-variable.js +7 -3
- package/lib/browser/task-context-variable.js.map +1 -1
- 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 +1 -1
- package/lib/common/change-set.js.map +1 -1
- package/lib/common/chat-agents-variable-contribution.d.ts.map +1 -1
- package/lib/common/chat-agents-variable-contribution.js +17 -1
- package/lib/common/chat-agents-variable-contribution.js.map +1 -1
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +3 -3
- package/lib/common/chat-agents.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-request-parser.d.ts +8 -1
- package/lib/common/chat-request-parser.d.ts.map +1 -1
- package/lib/common/chat-request-parser.js +29 -1
- package/lib/common/chat-request-parser.js.map +1 -1
- package/lib/common/chat-request-parser.spec.js +51 -0
- package/lib/common/chat-request-parser.spec.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-naming-service.d.ts.map +1 -1
- package/lib/common/chat-session-naming-service.js +11 -3
- package/lib/common/chat-session-naming-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/chat-session-summary-agent-prompt.js +1 -1
- package/lib/common/chat-session-summary-agent-prompt.js.map +1 -1
- package/lib/common/chat-session-summary-agent.d.ts.map +1 -1
- package/lib/common/chat-session-summary-agent.js +2 -1
- package/lib/common/chat-session-summary-agent.js.map +1 -1
- package/lib/common/chat-tool-preferences.js +1 -1
- package/lib/common/chat-tool-preferences.js.map +1 -1
- package/lib/common/image-context-variable.d.ts.map +1 -1
- package/lib/common/image-context-variable.js +21 -6
- package/lib/common/image-context-variable.js.map +1 -1
- 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 +29 -4
- package/src/browser/change-set-file-service.ts +3 -3
- package/src/browser/chat-session-store-impl.ts +326 -0
- package/src/browser/file-chat-variable-contribution.ts +2 -2
- package/src/browser/image-context-variable-contribution.ts +1 -1
- package/src/browser/task-context-service.ts +9 -3
- package/src/browser/task-context-variable.ts +8 -3
- package/src/common/change-set-element-deserializer.ts +90 -0
- package/src/common/change-set.ts +10 -2
- package/src/common/chat-agents-variable-contribution.ts +2 -2
- package/src/common/chat-agents.ts +4 -4
- 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-request-parser.spec.ts +61 -0
- package/src/common/chat-request-parser.ts +40 -1
- package/src/common/chat-service-deletion.spec.ts +269 -0
- package/src/common/chat-service.ts +227 -10
- package/src/common/chat-session-naming-service.ts +12 -4
- package/src/common/chat-session-store.ts +63 -0
- package/src/common/chat-session-summary-agent-prompt.ts +1 -1
- package/src/common/chat-session-summary-agent.ts +2 -1
- package/src/common/chat-tool-preferences.ts +1 -2
- package/src/common/image-context-variable.ts +21 -6
- package/src/common/index.ts +3 -0
|
@@ -204,4 +204,65 @@ describe('ChatRequestParserImpl', () => {
|
|
|
204
204
|
expect(result.toolRequests.get(testTool1.id)).to.deep.equal(testTool1);
|
|
205
205
|
expect(result.toolRequests.get(testTool2.id)).to.deep.equal(testTool2);
|
|
206
206
|
});
|
|
207
|
+
|
|
208
|
+
it('parses simple command without arguments', async () => {
|
|
209
|
+
const req: ChatRequest = {
|
|
210
|
+
text: '/hello'
|
|
211
|
+
};
|
|
212
|
+
const context: ChatContext = { variables: [] };
|
|
213
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
214
|
+
|
|
215
|
+
expect(result.parts.length).to.equal(1);
|
|
216
|
+
expect(result.parts[0] instanceof ParsedChatRequestVariablePart).to.be.true;
|
|
217
|
+
const varPart = result.parts[0] as ParsedChatRequestVariablePart;
|
|
218
|
+
expect(varPart.variableName).to.equal('prompt');
|
|
219
|
+
expect(varPart.variableArg).to.equal('hello');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('parses command with single argument', async () => {
|
|
223
|
+
const req: ChatRequest = {
|
|
224
|
+
text: '/explain topic'
|
|
225
|
+
};
|
|
226
|
+
const context: ChatContext = { variables: [] };
|
|
227
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
228
|
+
|
|
229
|
+
expect(result.parts.length).to.equal(1);
|
|
230
|
+
const varPart = result.parts[0] as ParsedChatRequestVariablePart;
|
|
231
|
+
expect(varPart.variableName).to.equal('prompt');
|
|
232
|
+
expect(varPart.variableArg).to.equal('explain|topic');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('parses command with multiple arguments', async () => {
|
|
236
|
+
const req: ChatRequest = {
|
|
237
|
+
text: '/compare item1 item2'
|
|
238
|
+
};
|
|
239
|
+
const context: ChatContext = { variables: [] };
|
|
240
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
241
|
+
|
|
242
|
+
const varPart = result.parts[0] as ParsedChatRequestVariablePart;
|
|
243
|
+
expect(varPart.variableName).to.equal('prompt');
|
|
244
|
+
expect(varPart.variableArg).to.equal('compare|item1 item2');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('parses command with quoted arguments', async () => {
|
|
248
|
+
const req: ChatRequest = {
|
|
249
|
+
text: '/cmd "arg with spaces" other'
|
|
250
|
+
};
|
|
251
|
+
const context: ChatContext = { variables: [] };
|
|
252
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
253
|
+
|
|
254
|
+
const varPart = result.parts[0] as ParsedChatRequestVariablePart;
|
|
255
|
+
expect(varPart.variableArg).to.equal('cmd|"arg with spaces" other');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('handles command with escaped quotes', async () => {
|
|
259
|
+
const req: ChatRequest = {
|
|
260
|
+
text: '/cmd "arg with \\"quote\\"" other'
|
|
261
|
+
};
|
|
262
|
+
const context: ChatContext = { variables: [] };
|
|
263
|
+
const result = await parser.parseChatRequest(req, ChatAgentLocation.Panel, context);
|
|
264
|
+
|
|
265
|
+
const varPart = result.parts[0] as ParsedChatRequestVariablePart;
|
|
266
|
+
expect(varPart.variableArg).to.equal('cmd|"arg with \\"quote\\"" other');
|
|
267
|
+
});
|
|
207
268
|
});
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
ParsedChatRequestTextPart,
|
|
32
32
|
ParsedChatRequestVariablePart,
|
|
33
33
|
chatVariableLeader,
|
|
34
|
+
chatSubcommandLeader,
|
|
34
35
|
OffsetRange,
|
|
35
36
|
ParsedChatRequest,
|
|
36
37
|
ParsedChatRequestPart,
|
|
@@ -42,6 +43,7 @@ const agentReg = /^@([\w_\-\.]+)(?=(\s|$|\b))/i; // An @-agent
|
|
|
42
43
|
const functionReg = /^~([\w_\-\.]+)(?=(\s|$|\b))/i; // A ~ tool function
|
|
43
44
|
const functionPromptFormatReg = /^\~\{\s*(.*?)\s*\}/i; // A ~{} prompt-format tool function
|
|
44
45
|
const variableReg = /^#([\w_\-]+)(?::([\w_\-_\/\\.:]+))?(?=(\s|$|\b))/i; // A #-variable with an optional : arg (#file:workspace/path/name.ext)
|
|
46
|
+
const commandReg = /^\/([\w_\-]+)(?:\s+(.+?))?(?=\s*$)/; // A /-command with optional arguments (/commandname arg1 arg2)
|
|
45
47
|
|
|
46
48
|
export const ChatRequestParser = Symbol('ChatRequestParser');
|
|
47
49
|
export interface ChatRequestParser {
|
|
@@ -114,7 +116,21 @@ export class ChatRequestParserImpl implements ChatRequestParser {
|
|
|
114
116
|
let newPart: ParsedChatRequestPart | undefined;
|
|
115
117
|
|
|
116
118
|
if (previousChar.match(/\s/) || i === 0) {
|
|
117
|
-
if (char ===
|
|
119
|
+
if (char === chatSubcommandLeader) {
|
|
120
|
+
// Try to parse as command - commands are syntactic sugar for #prompt:commandName|args
|
|
121
|
+
const commandPart = this.tryToParseCommand(
|
|
122
|
+
message.slice(i),
|
|
123
|
+
i,
|
|
124
|
+
parts
|
|
125
|
+
);
|
|
126
|
+
if (commandPart) {
|
|
127
|
+
newPart = commandPart;
|
|
128
|
+
const variable = this.variableService.getVariable(commandPart.variableName);
|
|
129
|
+
if (variable) {
|
|
130
|
+
variables.set(variable.name, variable);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else if (char === chatFunctionLeader) {
|
|
118
134
|
const functionPart = this.tryToParseFunction(
|
|
119
135
|
message.slice(i),
|
|
120
136
|
i
|
|
@@ -248,6 +264,29 @@ export class ChatRequestParserImpl implements ChatRequestParser {
|
|
|
248
264
|
return new ParsedChatRequestVariablePart(varRange, name, variableArg);
|
|
249
265
|
}
|
|
250
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Try to parse a command at the start of the given message.
|
|
269
|
+
*
|
|
270
|
+
* Commands are syntactic sugar for `#prompt:commandName|args`.
|
|
271
|
+
* The prompt variable resolver will handle the actual resolution.
|
|
272
|
+
*/
|
|
273
|
+
protected tryToParseCommand(
|
|
274
|
+
message: string,
|
|
275
|
+
offset: number,
|
|
276
|
+
_parts: ReadonlyArray<ParsedChatRequestPart>
|
|
277
|
+
): ParsedChatRequestVariablePart | undefined {
|
|
278
|
+
const nextCommandMatch = message.match(commandReg);
|
|
279
|
+
if (!nextCommandMatch) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const [full, commandName, commandArgs] = nextCommandMatch;
|
|
284
|
+
const commandRange = offsetRange(offset, offset + full.length);
|
|
285
|
+
|
|
286
|
+
const variableArg = commandArgs ? `${commandName}|${commandArgs}` : commandName;
|
|
287
|
+
return new ParsedChatRequestVariablePart(commandRange, 'prompt', variableArg);
|
|
288
|
+
}
|
|
289
|
+
|
|
251
290
|
private tryToParseFunction(message: string, offset: number): ParsedChatRequestFunctionPart | undefined {
|
|
252
291
|
// Support both the and chat and prompt formats for functions
|
|
253
292
|
const nextFunctionMatch = message.match(functionPromptFormatReg) || message.match(functionReg);
|
|
@@ -0,0 +1,269 @@
|
|
|
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 { ChatContentDeserializerRegistry, ChatContentDeserializerRegistryImpl, DefaultChatContentDeserializerContribution } from './chat-content-deserializer';
|
|
26
|
+
import { ChangeSetElementDeserializerRegistry, ChangeSetElementDeserializerRegistryImpl } from './change-set-element-deserializer';
|
|
27
|
+
import { ChatAgentLocation } from './chat-agents';
|
|
28
|
+
import { ChatModel } from './chat-model';
|
|
29
|
+
import { SerializedChatData } from './chat-model-serialization';
|
|
30
|
+
|
|
31
|
+
describe('ChatService Session Deletion', () => {
|
|
32
|
+
let chatService: ChatServiceImpl;
|
|
33
|
+
let sessionStore: MockChatSessionStore;
|
|
34
|
+
let container: Container;
|
|
35
|
+
|
|
36
|
+
class MockChatSessionStore implements ChatSessionStore {
|
|
37
|
+
public deletedSessions: string[] = [];
|
|
38
|
+
public storedSessions: Array<ChatModel | ChatModelWithMetadata> = [];
|
|
39
|
+
|
|
40
|
+
async storeSessions(...sessions: Array<ChatModel | ChatModelWithMetadata>): Promise<void> {
|
|
41
|
+
this.storedSessions = sessions;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async readSession(sessionId: string): Promise<SerializedChatData | undefined> {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async deleteSession(sessionId: string): Promise<void> {
|
|
49
|
+
this.deletedSessions.push(sessionId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async clearAllSessions(): Promise<void> {
|
|
53
|
+
this.deletedSessions = [];
|
|
54
|
+
this.storedSessions = [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getSessionIndex(): Promise<ChatSessionIndex> {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async setSessionTitle(sessionId: string, title: string): Promise<void> {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class MockChatAgentService {
|
|
66
|
+
getAgent(): undefined {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
getAgents(): never[] {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
class MockChatRequestParser {
|
|
75
|
+
parseChatRequest(): { parts: never[]; text: string } {
|
|
76
|
+
return { parts: [], text: '' };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class MockAIVariableService {
|
|
81
|
+
resolveVariables(): Promise<unknown[]> {
|
|
82
|
+
return Promise.resolve([]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class MockLogger {
|
|
87
|
+
error(): void { }
|
|
88
|
+
warn(): void { }
|
|
89
|
+
info(): void { }
|
|
90
|
+
debug(): void { }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
container = new Container();
|
|
95
|
+
sessionStore = new MockChatSessionStore();
|
|
96
|
+
|
|
97
|
+
container.bind(ChatSessionStore).toConstantValue(sessionStore);
|
|
98
|
+
container.bind(ChatAgentService).toConstantValue(new MockChatAgentService() as unknown as ChatAgentService);
|
|
99
|
+
container.bind(ChatRequestParser).toConstantValue(new MockChatRequestParser() as unknown as ChatRequestParser);
|
|
100
|
+
container.bind(AIVariableService).toConstantValue(new MockAIVariableService() as unknown as AIVariableService);
|
|
101
|
+
container.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
|
|
102
|
+
|
|
103
|
+
// Bind deserializer registries
|
|
104
|
+
const contentRegistry = new ChatContentDeserializerRegistryImpl();
|
|
105
|
+
new DefaultChatContentDeserializerContribution().registerDeserializers(contentRegistry);
|
|
106
|
+
container.bind(ChatContentDeserializerRegistry).toConstantValue(contentRegistry);
|
|
107
|
+
container.bind(ChangeSetElementDeserializerRegistry).toConstantValue(new ChangeSetElementDeserializerRegistryImpl());
|
|
108
|
+
container.bind(ChatServiceImpl).toSelf().inSingletonScope();
|
|
109
|
+
|
|
110
|
+
chatService = container.get(ChatServiceImpl);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('deleteSession', () => {
|
|
114
|
+
it('should delete session from memory and persistent storage', async () => {
|
|
115
|
+
// Create a session
|
|
116
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
117
|
+
expect(chatService.getSessions()).to.have.lengthOf(1);
|
|
118
|
+
|
|
119
|
+
// Delete the session (now returns a Promise)
|
|
120
|
+
await chatService.deleteSession(session.id);
|
|
121
|
+
|
|
122
|
+
// Verify it's removed from memory
|
|
123
|
+
expect(chatService.getSessions()).to.have.lengthOf(0);
|
|
124
|
+
|
|
125
|
+
// Verify it's deleted from persistent storage
|
|
126
|
+
expect(sessionStore.deletedSessions).to.include(session.id);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should emit SessionDeletedEvent when session is deleted', done => {
|
|
130
|
+
// Create a session
|
|
131
|
+
const session = chatService.createSession(ChatAgentLocation.Panel);
|
|
132
|
+
|
|
133
|
+
// Listen for deletion event
|
|
134
|
+
chatService.onSessionEvent(event => {
|
|
135
|
+
if (event.type === 'deleted') {
|
|
136
|
+
expect(event.sessionId).to.equal(session.id);
|
|
137
|
+
done();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Delete the session
|
|
142
|
+
chatService.deleteSession(session.id);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should handle deletion when session store is not available', async () => {
|
|
146
|
+
// Create a new service without session store
|
|
147
|
+
const containerWithoutStore = new Container();
|
|
148
|
+
containerWithoutStore.bind(ChatAgentService).toConstantValue(new MockChatAgentService() as unknown as ChatAgentService);
|
|
149
|
+
containerWithoutStore.bind(ChatRequestParser).toConstantValue(new MockChatRequestParser() as unknown as ChatRequestParser);
|
|
150
|
+
containerWithoutStore.bind(AIVariableService).toConstantValue(new MockAIVariableService() as unknown as AIVariableService);
|
|
151
|
+
containerWithoutStore.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
|
|
152
|
+
|
|
153
|
+
// Bind deserializer registries
|
|
154
|
+
const contentRegistry = new ChatContentDeserializerRegistryImpl();
|
|
155
|
+
new DefaultChatContentDeserializerContribution().registerDeserializers(contentRegistry);
|
|
156
|
+
containerWithoutStore.bind(ChatContentDeserializerRegistry).toConstantValue(contentRegistry);
|
|
157
|
+
containerWithoutStore.bind(ChangeSetElementDeserializerRegistry).toConstantValue(new ChangeSetElementDeserializerRegistryImpl());
|
|
158
|
+
|
|
159
|
+
containerWithoutStore.bind(ChatServiceImpl).toSelf().inSingletonScope();
|
|
160
|
+
|
|
161
|
+
const serviceWithoutStore = containerWithoutStore.get(ChatServiceImpl);
|
|
162
|
+
|
|
163
|
+
// Create and delete a session - should not throw
|
|
164
|
+
const session = serviceWithoutStore.createSession(ChatAgentLocation.Panel);
|
|
165
|
+
await serviceWithoutStore.deleteSession(session.id);
|
|
166
|
+
|
|
167
|
+
// Verify session is still removed from memory
|
|
168
|
+
expect(serviceWithoutStore.getSessions()).to.have.lengthOf(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should attempt storage deletion even for non-existent in-memory sessions', async () => {
|
|
172
|
+
const initialDeletedCount = sessionStore.deletedSessions.length;
|
|
173
|
+
const nonExistentId = 'non-existent-id';
|
|
174
|
+
|
|
175
|
+
// Try to delete non-existent session (could be a persisted-only session)
|
|
176
|
+
await chatService.deleteSession(nonExistentId);
|
|
177
|
+
|
|
178
|
+
// Verify storage deletion was attempted even though session not in memory
|
|
179
|
+
expect(sessionStore.deletedSessions).to.include(nonExistentId);
|
|
180
|
+
expect(sessionStore.deletedSessions).to.have.lengthOf(initialDeletedCount + 1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should handle deleting active session', () => {
|
|
184
|
+
// Create two sessions
|
|
185
|
+
const session1 = chatService.createSession(ChatAgentLocation.Panel);
|
|
186
|
+
const session2 = chatService.createSession(ChatAgentLocation.Panel);
|
|
187
|
+
|
|
188
|
+
// Ensure session2 is active (it should be by default as the latest)
|
|
189
|
+
expect(chatService.getActiveSession()?.id).to.equal(session2.id);
|
|
190
|
+
|
|
191
|
+
// Delete session1 (not active)
|
|
192
|
+
chatService.deleteSession(session1.id);
|
|
193
|
+
|
|
194
|
+
// Verify session2 is still active
|
|
195
|
+
const activeSession = chatService.getActiveSession();
|
|
196
|
+
expect(activeSession).to.not.be.undefined;
|
|
197
|
+
expect(activeSession?.id).to.equal(session2.id);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should handle storage deletion errors gracefully', async () => {
|
|
201
|
+
// Create a session store that throws errors
|
|
202
|
+
const errorStore = new MockChatSessionStore();
|
|
203
|
+
errorStore.deleteSession = async () => {
|
|
204
|
+
throw new Error('Storage error');
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const errorContainer = new Container();
|
|
208
|
+
errorContainer.bind(ChatSessionStore).toConstantValue(errorStore);
|
|
209
|
+
errorContainer.bind(ChatAgentService).toConstantValue(new MockChatAgentService() as unknown as ChatAgentService);
|
|
210
|
+
errorContainer.bind(ChatRequestParser).toConstantValue(new MockChatRequestParser() as unknown as ChatRequestParser);
|
|
211
|
+
errorContainer.bind(AIVariableService).toConstantValue(new MockAIVariableService() as unknown as AIVariableService);
|
|
212
|
+
errorContainer.bind(ILogger).toConstantValue(new MockLogger() as unknown as ILogger);
|
|
213
|
+
|
|
214
|
+
// Bind deserializer registries
|
|
215
|
+
const contentRegistry = new ChatContentDeserializerRegistryImpl();
|
|
216
|
+
new DefaultChatContentDeserializerContribution().registerDeserializers(contentRegistry);
|
|
217
|
+
errorContainer.bind(ChatContentDeserializerRegistry).toConstantValue(contentRegistry);
|
|
218
|
+
errorContainer.bind(ChangeSetElementDeserializerRegistry).toConstantValue(new ChangeSetElementDeserializerRegistryImpl());
|
|
219
|
+
|
|
220
|
+
errorContainer.bind(ChatServiceImpl).toSelf().inSingletonScope();
|
|
221
|
+
|
|
222
|
+
const errorService = errorContainer.get(ChatServiceImpl);
|
|
223
|
+
|
|
224
|
+
// Create and delete a session - should not throw even with storage error
|
|
225
|
+
const session = errorService.createSession(ChatAgentLocation.Panel);
|
|
226
|
+
await errorService.deleteSession(session.id);
|
|
227
|
+
|
|
228
|
+
// Verify session is still removed from memory despite storage error
|
|
229
|
+
expect(errorService.getSessions()).to.have.lengthOf(0);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should delete persisted-only sessions (not loaded in memory)', async () => {
|
|
233
|
+
// This simulates deleting a session from the "Show Chats..." dialog
|
|
234
|
+
// when the session is persisted but not currently loaded into memory
|
|
235
|
+
const persistedSessionId = 'persisted-session-123';
|
|
236
|
+
|
|
237
|
+
// Verify session is not in memory
|
|
238
|
+
expect(chatService.getSessions().find(s => s.id === persistedSessionId)).to.be.undefined;
|
|
239
|
+
|
|
240
|
+
// Delete the persisted-only session
|
|
241
|
+
await chatService.deleteSession(persistedSessionId);
|
|
242
|
+
|
|
243
|
+
// Verify it was still deleted from storage (even though not in memory)
|
|
244
|
+
expect(sessionStore.deletedSessions).to.include(persistedSessionId);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should not fire SessionDeletedEvent for persisted-only sessions', async () => {
|
|
248
|
+
// When deleting a persisted-only session (not in memory),
|
|
249
|
+
// we shouldn't fire the event since no in-memory state changed
|
|
250
|
+
const persistedSessionId = 'persisted-session-456';
|
|
251
|
+
let eventFired = false;
|
|
252
|
+
|
|
253
|
+
// Listen for deletion event
|
|
254
|
+
chatService.onSessionEvent(event => {
|
|
255
|
+
if (event.type === 'deleted' && event.sessionId === persistedSessionId) {
|
|
256
|
+
eventFired = true;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Delete the persisted-only session
|
|
261
|
+
await chatService.deleteSession(persistedSessionId);
|
|
262
|
+
|
|
263
|
+
// Event should not have been fired since session wasn't in memory
|
|
264
|
+
expect(eventFired).to.be.false;
|
|
265
|
+
// But storage deletion should still have happened
|
|
266
|
+
expect(sessionStore.deletedSessions).to.include(persistedSessionId);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|