Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/test/claudeSessionStateService.spec.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import sinon from 'sinon';
7
import { afterEach, assert, beforeEach, describe, it } from 'vitest';
8
import type { ClaudeFolderInfo } from '../../common/claudeFolderInfo';
9
import { parseClaudeModelId } from '../claudeModelId';
10
import type { SessionStateChangeEvent } from '../../common/claudeSessionStateService';
11
import { ClaudeSessionStateService } from '../claudeSessionStateService';
12
13
const OPUS_4 = parseClaudeModelId('claude-opus-4-20250514');
14
const HAIKU_3_5 = parseClaudeModelId('claude-haiku-3-5-20250514');
15
16
describe('ClaudeSessionStateService', () => {
17
let service: ClaudeSessionStateService;
18
19
beforeEach(() => {
20
service = new ClaudeSessionStateService();
21
});
22
23
afterEach(() => {
24
service.dispose();
25
sinon.restore();
26
});
27
28
describe('getModelIdForSession', () => {
29
it('should return undefined when no model is set for a session', () => {
30
const modelId = service.getModelIdForSession('session-1');
31
assert.strictEqual(modelId, undefined);
32
});
33
34
it('should return the set model when one has been set for a session', () => {
35
service.setModelIdForSession('session-1', OPUS_4);
36
const modelId = service.getModelIdForSession('session-1');
37
assert.strictEqual(modelId, OPUS_4);
38
});
39
40
it('should return different models for different sessions', () => {
41
service.setModelIdForSession('session-1', OPUS_4);
42
service.setModelIdForSession('session-2', HAIKU_3_5);
43
44
const modelId1 = service.getModelIdForSession('session-1');
45
const modelId2 = service.getModelIdForSession('session-2');
46
47
assert.strictEqual(modelId1, OPUS_4);
48
assert.strictEqual(modelId2, HAIKU_3_5);
49
});
50
51
it('should return undefined when model is explicitly set to undefined', () => {
52
service.setModelIdForSession('session-1', OPUS_4);
53
service.setModelIdForSession('session-1', undefined);
54
55
const modelId = service.getModelIdForSession('session-1');
56
assert.strictEqual(modelId, undefined);
57
});
58
});
59
60
describe('setModelIdForSession', () => {
61
it('should fire onDidChangeSessionState event when model is set', () => {
62
const events: SessionStateChangeEvent[] = [];
63
service.onDidChangeSessionState(e => events.push(e));
64
65
service.setModelIdForSession('session-1', OPUS_4);
66
67
assert.strictEqual(events.length, 1);
68
assert.strictEqual(events[0].sessionId, 'session-1');
69
assert.strictEqual(events[0].modelId, OPUS_4);
70
assert.strictEqual(events[0].permissionMode, undefined);
71
});
72
73
it('should preserve permission mode when setting model', () => {
74
service.setPermissionModeForSession('session-1', 'bypassPermissions');
75
service.setModelIdForSession('session-1', OPUS_4);
76
77
const permissionMode = service.getPermissionModeForSession('session-1');
78
assert.strictEqual(permissionMode, 'bypassPermissions');
79
});
80
});
81
82
describe('getPermissionModeForSession', () => {
83
it('should return default acceptEdits when no mode is set', () => {
84
const mode = service.getPermissionModeForSession('session-1');
85
assert.strictEqual(mode, 'acceptEdits');
86
});
87
88
it('should return the set permission mode', () => {
89
service.setPermissionModeForSession('session-1', 'bypassPermissions');
90
const mode = service.getPermissionModeForSession('session-1');
91
assert.strictEqual(mode, 'bypassPermissions');
92
});
93
94
it('should return different modes for different sessions', () => {
95
service.setPermissionModeForSession('session-1', 'bypassPermissions');
96
service.setPermissionModeForSession('session-2', 'default');
97
98
const mode1 = service.getPermissionModeForSession('session-1');
99
const mode2 = service.getPermissionModeForSession('session-2');
100
101
assert.strictEqual(mode1, 'bypassPermissions');
102
assert.strictEqual(mode2, 'default');
103
});
104
});
105
106
describe('setPermissionModeForSession', () => {
107
it('should fire onDidChangeSessionState event when permission mode is set', () => {
108
const events: SessionStateChangeEvent[] = [];
109
service.onDidChangeSessionState(e => events.push(e));
110
111
service.setPermissionModeForSession('session-1', 'bypassPermissions');
112
113
assert.strictEqual(events.length, 1);
114
assert.strictEqual(events[0].sessionId, 'session-1');
115
assert.strictEqual(events[0].permissionMode, 'bypassPermissions');
116
assert.strictEqual(events[0].modelId, undefined);
117
});
118
119
it('should preserve model id when setting permission mode', () => {
120
service.setModelIdForSession('session-1', OPUS_4);
121
service.setPermissionModeForSession('session-1', 'bypassPermissions');
122
123
const modelId = service.getModelIdForSession('session-1');
124
assert.strictEqual(modelId, OPUS_4);
125
});
126
});
127
128
describe('getFolderInfoForSession', () => {
129
it('should return undefined when no folder info is set', () => {
130
const folderInfo = service.getFolderInfoForSession('session-1');
131
assert.strictEqual(folderInfo, undefined);
132
});
133
134
it('should return the set folder info', () => {
135
const info: ClaudeFolderInfo = { cwd: '/home/user', additionalDirectories: ['/tmp'] };
136
service.setFolderInfoForSession('session-1', info);
137
const folderInfo = service.getFolderInfoForSession('session-1');
138
assert.deepStrictEqual(folderInfo, info);
139
});
140
});
141
142
describe('setFolderInfoForSession', () => {
143
it('should fire onDidChangeSessionState event when folder info is set', () => {
144
const events: SessionStateChangeEvent[] = [];
145
service.onDidChangeSessionState(e => events.push(e));
146
147
const info: ClaudeFolderInfo = { cwd: '/home/user', additionalDirectories: [] };
148
service.setFolderInfoForSession('session-1', info);
149
150
assert.strictEqual(events.length, 1);
151
assert.strictEqual(events[0].sessionId, 'session-1');
152
assert.deepStrictEqual(events[0].folderInfo, info);
153
assert.strictEqual(events[0].modelId, undefined);
154
assert.strictEqual(events[0].permissionMode, undefined);
155
});
156
157
it('should not fire event when folder info is unchanged', () => {
158
const info: ClaudeFolderInfo = { cwd: '/home/user', additionalDirectories: ['/tmp'] };
159
service.setFolderInfoForSession('session-1', info);
160
161
const events: SessionStateChangeEvent[] = [];
162
service.onDidChangeSessionState(e => events.push(e));
163
164
service.setFolderInfoForSession('session-1', { cwd: '/home/user', additionalDirectories: ['/tmp'] });
165
assert.strictEqual(events.length, 0);
166
});
167
168
it('should fire event when cwd changes', () => {
169
service.setFolderInfoForSession('session-1', { cwd: '/home/user', additionalDirectories: [] });
170
171
const events: SessionStateChangeEvent[] = [];
172
service.onDidChangeSessionState(e => events.push(e));
173
174
service.setFolderInfoForSession('session-1', { cwd: '/home/other', additionalDirectories: [] });
175
assert.strictEqual(events.length, 1);
176
});
177
178
it('should fire event when additionalDirectories change', () => {
179
service.setFolderInfoForSession('session-1', { cwd: '/home/user', additionalDirectories: ['/tmp'] });
180
181
const events: SessionStateChangeEvent[] = [];
182
service.onDidChangeSessionState(e => events.push(e));
183
184
service.setFolderInfoForSession('session-1', { cwd: '/home/user', additionalDirectories: ['/tmp', '/var'] });
185
assert.strictEqual(events.length, 1);
186
});
187
188
it('should preserve other state when setting folder info', () => {
189
service.setModelIdForSession('session-1', OPUS_4);
190
service.setPermissionModeForSession('session-1', 'bypassPermissions');
191
service.setFolderInfoForSession('session-1', { cwd: '/home/user', additionalDirectories: [] });
192
193
const modelId = service.getModelIdForSession('session-1');
194
assert.strictEqual(modelId, OPUS_4);
195
const permissionMode = service.getPermissionModeForSession('session-1');
196
assert.strictEqual(permissionMode, 'bypassPermissions');
197
});
198
});
199
200
describe('getReasoningEffortForSession', () => {
201
it('should return undefined when no reasoning effort is set', () => {
202
const effort = service.getReasoningEffortForSession('session-1');
203
assert.strictEqual(effort, undefined);
204
});
205
206
it('should return the set reasoning effort', () => {
207
service.setReasoningEffortForSession('session-1', 'high');
208
const effort = service.getReasoningEffortForSession('session-1');
209
assert.strictEqual(effort, 'high');
210
});
211
212
it('should return different efforts for different sessions', () => {
213
service.setReasoningEffortForSession('session-1', 'high');
214
service.setReasoningEffortForSession('session-2', 'low');
215
216
assert.strictEqual(service.getReasoningEffortForSession('session-1'), 'high');
217
assert.strictEqual(service.getReasoningEffortForSession('session-2'), 'low');
218
});
219
});
220
221
describe('setReasoningEffortForSession', () => {
222
it('should allow setting a reasoning effort', () => {
223
service.setReasoningEffortForSession('session-1', 'medium');
224
assert.strictEqual(service.getReasoningEffortForSession('session-1'), 'medium');
225
});
226
227
it('should allow clearing a reasoning effort', () => {
228
service.setReasoningEffortForSession('session-1', 'high');
229
service.setReasoningEffortForSession('session-1', undefined);
230
assert.strictEqual(service.getReasoningEffortForSession('session-1'), undefined);
231
});
232
233
it('should not update state when effort is unchanged', () => {
234
service.setReasoningEffortForSession('session-1', 'high');
235
const stateBefore = service.getReasoningEffortForSession('session-1');
236
service.setReasoningEffortForSession('session-1', 'high');
237
assert.strictEqual(service.getReasoningEffortForSession('session-1'), stateBefore);
238
});
239
240
it('should preserve other state when setting reasoning effort', () => {
241
service.setModelIdForSession('session-1', OPUS_4);
242
service.setPermissionModeForSession('session-1', 'bypassPermissions');
243
244
service.setReasoningEffortForSession('session-1', 'high');
245
246
assert.strictEqual(service.getModelIdForSession('session-1'), OPUS_4);
247
assert.strictEqual(service.getPermissionModeForSession('session-1'), 'bypassPermissions');
248
});
249
250
it('should not fire onDidChangeSessionState event', () => {
251
const events: SessionStateChangeEvent[] = [];
252
service.onDidChangeSessionState(e => events.push(e));
253
254
service.setReasoningEffortForSession('session-1', 'high');
255
256
assert.strictEqual(events.length, 0);
257
});
258
259
it('should initialize defaults when session has no prior state', () => {
260
service.setReasoningEffortForSession('new-session', 'medium');
261
262
assert.strictEqual(service.getModelIdForSession('new-session'), undefined);
263
assert.strictEqual(service.getPermissionModeForSession('new-session'), 'acceptEdits');
264
assert.strictEqual(service.getCapturingTokenForSession('new-session'), undefined);
265
assert.strictEqual(service.getFolderInfoForSession('new-session'), undefined);
266
assert.strictEqual(service.getUsageHandlerForSession('new-session'), undefined);
267
assert.strictEqual(service.getReasoningEffortForSession('new-session'), 'medium');
268
});
269
});
270
271
describe('dispose', () => {
272
it('should clear session state on dispose', () => {
273
service.setModelIdForSession('session-1', OPUS_4);
274
service.setPermissionModeForSession('session-1', 'bypassPermissions');
275
276
service.dispose();
277
278
// After dispose, getting state should return defaults (though event subscriptions won't work)
279
// We can't really test this fully without internal access, but we can verify it doesn't throw
280
const newService = new ClaudeSessionStateService();
281
const modelId = newService.getModelIdForSession('session-1');
282
assert.strictEqual(modelId, undefined);
283
newService.dispose();
284
});
285
});
286
287
describe('getUsageHandlerForSession', () => {
288
it('should return undefined when no usage handler is set', () => {
289
const handler = service.getUsageHandlerForSession('session-1');
290
assert.strictEqual(handler, undefined);
291
});
292
293
it('should return the set usage handler', () => {
294
const mockHandler = sinon.stub();
295
service.setUsageHandlerForSession('session-1', mockHandler);
296
const handler = service.getUsageHandlerForSession('session-1');
297
assert.strictEqual(handler, mockHandler);
298
});
299
300
it('should return different handlers for different sessions', () => {
301
const handler1 = sinon.stub();
302
const handler2 = sinon.stub();
303
service.setUsageHandlerForSession('session-1', handler1);
304
service.setUsageHandlerForSession('session-2', handler2);
305
306
const retrieved1 = service.getUsageHandlerForSession('session-1');
307
const retrieved2 = service.getUsageHandlerForSession('session-2');
308
309
assert.strictEqual(retrieved1, handler1);
310
assert.strictEqual(retrieved2, handler2);
311
});
312
});
313
314
describe('setUsageHandlerForSession', () => {
315
it('should allow setting a usage handler', () => {
316
const mockHandler = sinon.stub();
317
service.setUsageHandlerForSession('session-1', mockHandler);
318
319
const handler = service.getUsageHandlerForSession('session-1');
320
assert.strictEqual(handler, mockHandler);
321
});
322
323
it('should allow clearing a usage handler', () => {
324
const mockHandler = sinon.stub();
325
service.setUsageHandlerForSession('session-1', mockHandler);
326
service.setUsageHandlerForSession('session-1', undefined);
327
328
const handler = service.getUsageHandlerForSession('session-1');
329
assert.strictEqual(handler, undefined);
330
});
331
332
it('should preserve other state when setting usage handler', () => {
333
service.setModelIdForSession('session-1', OPUS_4);
334
service.setPermissionModeForSession('session-1', 'bypassPermissions');
335
336
const mockHandler = sinon.stub();
337
service.setUsageHandlerForSession('session-1', mockHandler);
338
339
const modelId = service.getModelIdForSession('session-1');
340
assert.strictEqual(modelId, OPUS_4);
341
const permissionMode = service.getPermissionModeForSession('session-1');
342
assert.strictEqual(permissionMode, 'bypassPermissions');
343
});
344
345
it('should allow usage handler to be called after setting', () => {
346
const mockHandler = sinon.stub();
347
service.setUsageHandlerForSession('session-1', mockHandler);
348
349
const handler = service.getUsageHandlerForSession('session-1');
350
handler?.({ promptTokens: 100, completionTokens: 50 });
351
352
assert.strictEqual(mockHandler.callCount, 1);
353
assert.deepStrictEqual(mockHandler.firstCall.args[0], { promptTokens: 100, completionTokens: 50 });
354
});
355
356
it('should not fire onDidChangeSessionState event', () => {
357
const events: SessionStateChangeEvent[] = [];
358
service.onDidChangeSessionState(e => events.push(e));
359
360
const mockHandler = sinon.stub();
361
service.setUsageHandlerForSession('session-1', mockHandler);
362
363
assert.strictEqual(events.length, 0);
364
});
365
366
it('should initialize defaults when session has no prior state', () => {
367
const mockHandler = sinon.stub();
368
service.setUsageHandlerForSession('new-session', mockHandler);
369
370
assert.strictEqual(service.getModelIdForSession('new-session'), undefined);
371
assert.strictEqual(service.getPermissionModeForSession('new-session'), 'acceptEdits');
372
assert.strictEqual(service.getCapturingTokenForSession('new-session'), undefined);
373
assert.strictEqual(service.getFolderInfoForSession('new-session'), undefined);
374
assert.strictEqual(service.getUsageHandlerForSession('new-session'), mockHandler);
375
});
376
});
377
});
378
379