Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts
5310 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 { localize, localize2 } from '../../../../../nls.js';
7
import { AgentSessionSection, IAgentSession, IAgentSessionSection, IMarshalledAgentSessionContext, isAgentSessionSection, isLocalAgentSessionItem, isMarshalledAgentSessionContext } from './agentSessionsModel.js';
8
import { Action2, MenuId, MenuRegistry } from '../../../../../platform/actions/common/actions.js';
9
import { Codicon } from '../../../../../base/common/codicons.js';
10
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
11
import { AGENT_SESSION_DELETE_ACTION_ID, AGENT_SESSION_RENAME_ACTION_ID, AgentSessionProviders, AgentSessionsViewerOrientation, IAgentSessionsControl } from './agentSessions.js';
12
import { IChatService } from '../../common/chatService/chatService.js';
13
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
14
import { IChatEditorOptions } from '../widgetHosts/editor/chatEditor.js';
15
import { ChatViewId, IChatWidgetService } from '../chat.js';
16
import { ACTIVE_GROUP, AUX_WINDOW_GROUP, PreferredGroup, SIDE_GROUP } from '../../../../services/editor/common/editorService.js';
17
import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js';
18
import { IWorkbenchLayoutService, Position } from '../../../../services/layout/browser/layoutService.js';
19
import { IAgentSessionsService } from './agentSessionsService.js';
20
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
21
import { ChatEditorInput, showClearEditingSessionConfirmation } from '../widgetHosts/editor/chatEditorInput.js';
22
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
23
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
24
import { ChatConfiguration } from '../../common/constants.js';
25
import { ACTION_ID_NEW_CHAT } from '../actions/chatActions.js';
26
import { IViewsService } from '../../../../services/views/common/viewsService.js';
27
import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';
28
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
29
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
30
import { AgentSessionsPicker } from './agentSessionsPicker.js';
31
import { ActiveEditorContext } from '../../../../common/contextkeys.js';
32
import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';
33
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
34
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
35
import { coalesce } from '../../../../../base/common/arrays.js';
36
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
37
import { IPaneCompositePartService } from '../../../../services/panecomposite/browser/panecomposite.js';
38
39
const AGENT_SESSIONS_CATEGORY = localize2('chatSessions', "Chat Agent Sessions");
40
41
//#region Chat View
42
43
export class ToggleShowAgentSessionsAction extends Action2 {
44
45
constructor() {
46
super({
47
id: 'workbench.action.chat.toggleShowAgentSessions',
48
title: localize2('chat.showSessions', "Show Sessions"),
49
toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
50
menu: {
51
id: MenuId.ChatWelcomeContext,
52
group: '0_sessions',
53
order: 2,
54
when: ChatContextKeys.inChatEditor.negate()
55
}
56
});
57
}
58
59
async run(accessor: ServicesAccessor): Promise<void> {
60
const configurationService = accessor.get(IConfigurationService);
61
const currentValue = configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsEnabled);
62
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, !currentValue);
63
}
64
}
65
66
const agentSessionsOrientationSubmenu = new MenuId('chatAgentSessionsOrientationSubmenu');
67
MenuRegistry.appendMenuItem(MenuId.ChatWelcomeContext, {
68
submenu: agentSessionsOrientationSubmenu,
69
title: localize2('chat.sessionsOrientation', "Sessions Orientation"),
70
group: '0_sessions',
71
order: 1,
72
when: ChatContextKeys.inChatEditor.negate()
73
});
74
75
export class SetAgentSessionsOrientationStackedAction extends Action2 {
76
77
constructor() {
78
super({
79
id: 'workbench.action.chat.setAgentSessionsOrientationStacked',
80
title: localize2('chat.sessionsOrientation.stacked', "Stacked"),
81
toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsOrientation}`, 'stacked'),
82
precondition: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
83
menu: {
84
id: agentSessionsOrientationSubmenu,
85
group: 'navigation',
86
order: 2
87
}
88
});
89
}
90
91
async run(accessor: ServicesAccessor): Promise<void> {
92
const commandService = accessor.get(ICommandService);
93
94
await commandService.executeCommand(HideAgentSessionsSidebar.ID);
95
}
96
}
97
98
export class SetAgentSessionsOrientationSideBySideAction extends Action2 {
99
100
constructor() {
101
super({
102
id: 'workbench.action.chat.setAgentSessionsOrientationSideBySide',
103
title: localize2('chat.sessionsOrientation.sideBySide', "Side by Side"),
104
toggled: ContextKeyExpr.notEquals(`config.${ChatConfiguration.ChatViewSessionsOrientation}`, 'stacked'),
105
precondition: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
106
menu: {
107
id: agentSessionsOrientationSubmenu,
108
group: 'navigation',
109
order: 1
110
}
111
});
112
}
113
114
async run(accessor: ServicesAccessor): Promise<void> {
115
const commandService = accessor.get(ICommandService);
116
117
await commandService.executeCommand(ShowAgentSessionsSidebar.ID);
118
}
119
}
120
121
export class PickAgentSessionAction extends Action2 {
122
123
constructor() {
124
super({
125
id: `workbench.action.chat.history`,
126
title: localize2('agentSessions.open', "Open Agent Session..."),
127
menu: [
128
{
129
id: MenuId.ViewTitle,
130
when: ContextKeyExpr.and(
131
ContextKeyExpr.equals('view', ChatViewId),
132
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, false)
133
),
134
group: 'navigation',
135
order: 2
136
},
137
{
138
id: MenuId.EditorTitle,
139
when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID),
140
}
141
],
142
category: AGENT_SESSIONS_CATEGORY,
143
icon: Codicon.history,
144
f1: true,
145
precondition: ChatContextKeys.enabled
146
});
147
}
148
149
async run(accessor: ServicesAccessor): Promise<void> {
150
const instantiationService = accessor.get(IInstantiationService);
151
152
const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker, undefined);
153
await agentSessionsPicker.pickAgentSession();
154
}
155
}
156
157
export class ArchiveAllAgentSessionsAction extends Action2 {
158
159
constructor() {
160
super({
161
id: 'workbench.action.chat.archiveAllAgentSessions',
162
title: localize2('archiveAll.label', "Archive All Workspace Agent Sessions"),
163
precondition: ChatContextKeys.enabled,
164
category: AGENT_SESSIONS_CATEGORY,
165
f1: true,
166
});
167
}
168
async run(accessor: ServicesAccessor) {
169
const agentSessionsService = accessor.get(IAgentSessionsService);
170
const dialogService = accessor.get(IDialogService);
171
172
const sessionsToArchive = agentSessionsService.model.sessions.filter(session => !session.isArchived());
173
if (sessionsToArchive.length === 0) {
174
return;
175
}
176
177
const confirmed = await dialogService.confirm({
178
message: sessionsToArchive.length === 1
179
? localize('archiveAllSessions.confirmSingle', "Are you sure you want to archive 1 agent session?")
180
: localize('archiveAllSessions.confirm', "Are you sure you want to archive {0} agent sessions?", sessionsToArchive.length),
181
detail: localize('archiveAllSessions.detail', "You can unarchive sessions later if needed from the Chat view."),
182
primaryButton: localize('archiveAllSessions.archive', "Archive")
183
});
184
185
if (!confirmed.confirmed) {
186
return;
187
}
188
189
for (const session of sessionsToArchive) {
190
session.setArchived(true);
191
}
192
}
193
}
194
195
export class MarkAllAgentSessionsReadAction extends Action2 {
196
197
constructor() {
198
super({
199
id: 'workbench.action.chat.markAllAgentSessionsRead',
200
title: localize2('markAllRead.label', "Mark All as Read"),
201
precondition: ChatContextKeys.enabled,
202
category: AGENT_SESSIONS_CATEGORY,
203
f1: true,
204
menu: {
205
id: MenuId.AgentSessionsContext,
206
group: '0_read',
207
order: 2,
208
when: ChatContextKeys.isArchivedAgentSession.negate() // no read state for archived sessions
209
}
210
});
211
}
212
async run(accessor: ServicesAccessor) {
213
const agentSessionsService = accessor.get(IAgentSessionsService);
214
215
const sessionsToMarkRead = agentSessionsService.model.sessions.filter(session => !session.isArchived() && !session.isRead());
216
if (sessionsToMarkRead.length === 0) {
217
return;
218
}
219
220
for (const session of sessionsToMarkRead) {
221
session.setRead(true);
222
}
223
}
224
}
225
226
const ConfirmArchiveStorageKey = 'chat.sessions.confirmArchive';
227
228
export class ArchiveAgentSessionSectionAction extends Action2 {
229
230
constructor() {
231
super({
232
id: 'agentSessionSection.archive',
233
title: localize2('archiveSection', "Archive All"),
234
icon: Codicon.archive,
235
menu: [{
236
id: MenuId.AgentSessionSectionToolbar,
237
group: 'navigation',
238
order: 1,
239
when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
240
}, {
241
id: MenuId.AgentSessionSectionContext,
242
group: '1_edit',
243
order: 2,
244
when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
245
}]
246
});
247
}
248
249
async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {
250
if (!context || !isAgentSessionSection(context)) {
251
return;
252
}
253
254
const dialogService = accessor.get(IDialogService);
255
const storageService = accessor.get(IStorageService);
256
257
const skipConfirmation = storageService.getBoolean(ConfirmArchiveStorageKey, StorageScope.PROFILE, false);
258
if (!skipConfirmation) {
259
const confirmed = await dialogService.confirm({
260
message: context.sessions.length === 1
261
? localize('archiveSectionSessions.confirmSingle', "Are you sure you want to archive 1 agent session from '{0}'?", context.label)
262
: localize('archiveSectionSessions.confirm', "Are you sure you want to archive {0} agent sessions from '{1}'?", context.sessions.length, context.label),
263
detail: localize('archiveSectionSessions.detail', "You can unarchive sessions later if needed from the sessions view."),
264
primaryButton: localize('archiveSectionSessions.archive', "Archive All"),
265
checkbox: {
266
label: localize('doNotAskAgain', "Do not ask me again")
267
}
268
});
269
270
if (!confirmed.confirmed) {
271
return;
272
}
273
274
if (confirmed.checkboxChecked) {
275
storageService.store(ConfirmArchiveStorageKey, true, StorageScope.PROFILE, StorageTarget.USER);
276
}
277
}
278
279
for (const session of context.sessions) {
280
session.setArchived(true);
281
}
282
}
283
}
284
285
export class UnarchiveAgentSessionSectionAction extends Action2 {
286
287
constructor() {
288
super({
289
id: 'agentSessionSection.unarchive',
290
title: localize2('unarchiveSection', "Unarchive All"),
291
icon: Codicon.unarchive,
292
menu: [{
293
id: MenuId.AgentSessionSectionToolbar,
294
group: 'navigation',
295
order: 1,
296
when: ChatContextKeys.agentSessionSection.isEqualTo(AgentSessionSection.Archived),
297
}, {
298
id: MenuId.AgentSessionSectionContext,
299
group: '1_edit',
300
order: 2,
301
when: ChatContextKeys.agentSessionSection.isEqualTo(AgentSessionSection.Archived),
302
}]
303
});
304
}
305
306
async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {
307
if (!context || !isAgentSessionSection(context)) {
308
return;
309
}
310
311
const dialogService = accessor.get(IDialogService);
312
const storageService = accessor.get(IStorageService);
313
314
const skipConfirmation = storageService.getBoolean(ConfirmArchiveStorageKey, StorageScope.PROFILE, false);
315
if (!skipConfirmation) {
316
const confirmed = await dialogService.confirm({
317
message: context.sessions.length === 1
318
? localize('unarchiveSectionSessions.confirmSingle', "Are you sure you want to unarchive 1 agent session?")
319
: localize('unarchiveSectionSessions.confirm', "Are you sure you want to unarchive {0} agent sessions?", context.sessions.length),
320
primaryButton: localize('unarchiveSectionSessions.unarchive', "Unarchive All"),
321
checkbox: {
322
label: localize('doNotAskAgain', "Do not ask me again")
323
}
324
});
325
326
if (!confirmed.confirmed) {
327
return;
328
}
329
330
if (confirmed.checkboxChecked) {
331
storageService.store(ConfirmArchiveStorageKey, true, StorageScope.PROFILE, StorageTarget.USER);
332
}
333
}
334
335
for (const session of context.sessions) {
336
session.setArchived(false);
337
}
338
}
339
}
340
341
export class MarkAgentSessionSectionReadAction extends Action2 {
342
343
constructor() {
344
super({
345
id: 'agentSessionSection.markRead',
346
title: localize2('markSectionRead', "Mark All as Read"),
347
menu: [{
348
id: MenuId.AgentSessionSectionContext,
349
group: '1_edit',
350
order: 1,
351
when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
352
}]
353
});
354
}
355
356
async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {
357
if (!context || !isAgentSessionSection(context)) {
358
return;
359
}
360
361
for (const session of context.sessions) {
362
session.setRead(true);
363
}
364
}
365
}
366
367
//#endregion
368
369
//#region Session Actions
370
371
abstract class BaseAgentSessionAction extends Action2 {
372
373
async run(accessor: ServicesAccessor, context?: IAgentSession | IMarshalledAgentSessionContext): Promise<void> {
374
const agentSessionsService = accessor.get(IAgentSessionsService);
375
const viewsService = accessor.get(IViewsService);
376
377
let sessions: IAgentSession[] = [];
378
if (isMarshalledAgentSessionContext(context)) {
379
sessions = coalesce((context.sessions ?? [context.session]).map(session => agentSessionsService.getSession(session.resource)));
380
} else if (context) {
381
sessions = [context];
382
}
383
384
if (sessions.length === 0) {
385
const chatView = viewsService.getActiveViewWithId<ChatViewPane>(ChatViewId);
386
const focused = chatView?.getFocusedSessions().at(0);
387
if (focused) {
388
sessions = [focused];
389
}
390
}
391
392
if (sessions.length > 0) {
393
await this.runWithSessions(sessions, accessor);
394
}
395
}
396
397
abstract runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> | void;
398
}
399
400
export class MarkAgentSessionUnreadAction extends BaseAgentSessionAction {
401
402
constructor() {
403
super({
404
id: 'agentSession.markUnread',
405
title: localize2('markUnread', "Mark as Unread"),
406
menu: {
407
id: MenuId.AgentSessionsContext,
408
group: '0_read',
409
order: 1,
410
when: ContextKeyExpr.and(
411
ChatContextKeys.isReadAgentSession,
412
ChatContextKeys.isArchivedAgentSession.negate() // no read state for archived sessions
413
),
414
}
415
});
416
}
417
418
runWithSessions(sessions: IAgentSession[]): void {
419
for (const session of sessions) {
420
session.setRead(false);
421
}
422
}
423
}
424
425
export class MarkAgentSessionReadAction extends BaseAgentSessionAction {
426
427
constructor() {
428
super({
429
id: 'agentSession.markRead',
430
title: localize2('markRead', "Mark as Read"),
431
menu: {
432
id: MenuId.AgentSessionsContext,
433
group: '0_read',
434
order: 1,
435
when: ContextKeyExpr.and(
436
ChatContextKeys.isReadAgentSession.negate(),
437
ChatContextKeys.isArchivedAgentSession.negate() // no read state for archived sessions
438
),
439
}
440
});
441
}
442
443
runWithSessions(sessions: IAgentSession[]): void {
444
for (const session of sessions) {
445
session.setRead(true);
446
}
447
}
448
}
449
450
export class ArchiveAgentSessionAction extends BaseAgentSessionAction {
451
452
constructor() {
453
super({
454
id: 'agentSession.archive',
455
title: localize2('archive', "Archive"),
456
icon: Codicon.archive,
457
keybinding: {
458
primary: KeyCode.Delete,
459
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
460
weight: KeybindingWeight.WorkbenchContrib + 1,
461
when: ContextKeyExpr.and(
462
ChatContextKeys.agentSessionsViewerFocused,
463
ChatContextKeys.isArchivedAgentSession.negate()
464
)
465
},
466
menu: [{
467
id: MenuId.AgentSessionItemToolbar,
468
group: 'navigation',
469
order: 1,
470
when: ChatContextKeys.isArchivedAgentSession.negate(),
471
}, {
472
id: MenuId.AgentSessionsContext,
473
group: '1_edit',
474
order: 2,
475
when: ChatContextKeys.isArchivedAgentSession.negate()
476
}]
477
});
478
}
479
480
async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {
481
const chatService = accessor.get(IChatService);
482
const dialogService = accessor.get(IDialogService);
483
484
// Archive all sessions
485
for (const session of sessions) {
486
const chatModel = chatService.getSession(session.resource);
487
if (chatModel && !await showClearEditingSessionConfirmation(chatModel, dialogService, {
488
isArchiveAction: true,
489
titleOverride: localize('archiveSession', "Archive chat with pending edits?"),
490
messageOverride: localize('archiveSessionDescription', "You have pending changes in this chat session.")
491
})) {
492
return;
493
}
494
495
session.setArchived(true);
496
}
497
}
498
}
499
500
export class UnarchiveAgentSessionAction extends BaseAgentSessionAction {
501
502
constructor() {
503
super({
504
id: 'agentSession.unarchive',
505
title: localize2('unarchive', "Unarchive"),
506
icon: Codicon.unarchive,
507
keybinding: {
508
primary: KeyMod.Shift | KeyCode.Delete,
509
mac: {
510
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace,
511
},
512
weight: KeybindingWeight.WorkbenchContrib + 1,
513
when: ContextKeyExpr.and(
514
ChatContextKeys.agentSessionsViewerFocused,
515
ChatContextKeys.isArchivedAgentSession
516
)
517
},
518
menu: [{
519
id: MenuId.AgentSessionItemToolbar,
520
group: 'navigation',
521
order: 1,
522
when: ChatContextKeys.isArchivedAgentSession,
523
}, {
524
id: MenuId.AgentSessionsContext,
525
group: '1_edit',
526
order: 2,
527
when: ChatContextKeys.isArchivedAgentSession,
528
}]
529
});
530
}
531
532
runWithSessions(sessions: IAgentSession[]): void {
533
for (const session of sessions) {
534
session.setArchived(false);
535
}
536
}
537
}
538
539
export class RenameAgentSessionAction extends BaseAgentSessionAction {
540
541
constructor() {
542
super({
543
id: AGENT_SESSION_RENAME_ACTION_ID,
544
title: localize2('rename', "Rename..."),
545
precondition: ChatContextKeys.hasMultipleAgentSessionsSelected.negate(),
546
keybinding: {
547
primary: KeyCode.F2,
548
mac: {
549
primary: KeyCode.Enter
550
},
551
weight: KeybindingWeight.WorkbenchContrib + 1,
552
when: ContextKeyExpr.and(
553
ChatContextKeys.agentSessionsViewerFocused,
554
ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local)
555
),
556
},
557
menu: {
558
id: MenuId.AgentSessionsContext,
559
group: '1_edit',
560
order: 3,
561
when: ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local)
562
}
563
});
564
}
565
566
async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {
567
const session = sessions.at(0);
568
if (!session) {
569
return;
570
}
571
572
const quickInputService = accessor.get(IQuickInputService);
573
const chatService = accessor.get(IChatService);
574
575
const title = await quickInputService.input({ prompt: localize('newChatTitle', "New agent session title"), value: session.label });
576
if (title) {
577
chatService.setChatSessionTitle(session.resource, title);
578
}
579
}
580
}
581
582
export class DeleteAgentSessionAction extends BaseAgentSessionAction {
583
584
constructor() {
585
super({
586
id: AGENT_SESSION_DELETE_ACTION_ID,
587
title: localize2('delete', "Delete..."),
588
menu: {
589
id: MenuId.AgentSessionsContext,
590
group: '1_edit',
591
order: 4,
592
when: ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local)
593
}
594
});
595
}
596
597
async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {
598
if (sessions.length === 0) {
599
return;
600
}
601
602
const chatService = accessor.get(IChatService);
603
const dialogService = accessor.get(IDialogService);
604
const widgetService = accessor.get(IChatWidgetService);
605
606
const confirmed = await dialogService.confirm({
607
message: sessions.length === 1
608
? localize('deleteSession.confirm', "Are you sure you want to delete this chat session?")
609
: localize('deleteSessions.confirm', "Are you sure you want to delete {0} chat sessions?", sessions.length),
610
detail: localize('deleteSession.detail', "This action cannot be undone."),
611
primaryButton: localize('deleteSession.delete', "Delete")
612
});
613
614
if (!confirmed.confirmed) {
615
return;
616
}
617
618
for (const session of sessions) {
619
620
// Clear chat widget
621
await widgetService.getWidgetBySessionResource(session.resource)?.clear();
622
623
// Remove from storage
624
await chatService.removeHistoryEntry(session.resource);
625
}
626
}
627
}
628
629
export class DeleteAllLocalSessionsAction extends Action2 {
630
631
constructor() {
632
super({
633
id: 'workbench.action.chat.clearHistory',
634
title: localize2('agentSessions.deleteAll', "Delete All Local Workspace Chat Sessions"),
635
precondition: ChatContextKeys.enabled,
636
category: AGENT_SESSIONS_CATEGORY,
637
f1: true,
638
});
639
}
640
641
async run(accessor: ServicesAccessor, ...args: unknown[]) {
642
const chatService = accessor.get(IChatService);
643
const widgetService = accessor.get(IChatWidgetService);
644
const dialogService = accessor.get(IDialogService);
645
const agentSessionsService = accessor.get(IAgentSessionsService);
646
647
const localSessionsCount = agentSessionsService.model.sessions.filter(session => isLocalAgentSessionItem(session)).length;
648
if (localSessionsCount === 0) {
649
return;
650
}
651
652
const confirmed = await dialogService.confirm({
653
message: localSessionsCount === 1
654
? localize('deleteAllChats.confirmSingle', "Are you sure you want to delete 1 local workspace chat session?")
655
: localize('deleteAllChats.confirm', "Are you sure you want to delete {0} local workspace chat sessions?", localSessionsCount),
656
detail: localize('deleteAllChats.detail', "This action cannot be undone."),
657
primaryButton: localize('deleteAllChats.button', "Delete All")
658
});
659
660
if (!confirmed.confirmed) {
661
return;
662
}
663
664
// Clear all chat widgets
665
await Promise.all(widgetService.getAllWidgets().map(widget => widget.clear()));
666
667
// Remove from storage
668
await chatService.clearAllHistoryEntries();
669
}
670
}
671
672
abstract class BaseOpenAgentSessionAction extends BaseAgentSessionAction {
673
674
async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {
675
const chatWidgetService = accessor.get(IChatWidgetService);
676
677
const targetGroup = this.getTargetGroup();
678
for (const session of sessions) {
679
const uri = session.resource;
680
681
await chatWidgetService.openSession(uri, targetGroup, {
682
...this.getOptions(),
683
pinned: true
684
});
685
}
686
}
687
688
protected abstract getTargetGroup(): PreferredGroup;
689
690
protected abstract getOptions(): IChatEditorOptions;
691
}
692
693
export class OpenAgentSessionInEditorGroupAction extends BaseOpenAgentSessionAction {
694
695
static readonly id = 'workbench.action.chat.openSessionInEditorGroup';
696
697
constructor() {
698
super({
699
id: OpenAgentSessionInEditorGroupAction.id,
700
title: localize2('chat.openSessionInEditorGroup.label', "Open as Editor"),
701
keybinding: {
702
primary: KeyMod.CtrlCmd | KeyCode.Enter,
703
mac: {
704
primary: KeyMod.WinCtrl | KeyCode.Enter
705
},
706
weight: KeybindingWeight.WorkbenchContrib + 1,
707
when: ChatContextKeys.agentSessionsViewerFocused,
708
},
709
menu: {
710
id: MenuId.AgentSessionsContext,
711
order: 1,
712
group: 'navigation'
713
}
714
});
715
}
716
717
protected getTargetGroup(): PreferredGroup {
718
return ACTIVE_GROUP;
719
}
720
721
protected getOptions(): IChatEditorOptions {
722
return {};
723
}
724
}
725
726
export class OpenAgentSessionInNewEditorGroupAction extends BaseOpenAgentSessionAction {
727
728
static readonly id = 'workbench.action.chat.openSessionInNewEditorGroup';
729
730
constructor() {
731
super({
732
id: OpenAgentSessionInNewEditorGroupAction.id,
733
title: localize2('chat.openSessionInNewEditorGroup.label', "Open to the Side"),
734
keybinding: {
735
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter,
736
mac: {
737
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter
738
},
739
weight: KeybindingWeight.WorkbenchContrib + 1,
740
when: ChatContextKeys.agentSessionsViewerFocused,
741
},
742
menu: {
743
id: MenuId.AgentSessionsContext,
744
order: 2,
745
group: 'navigation'
746
}
747
});
748
}
749
750
protected getTargetGroup(): PreferredGroup {
751
return SIDE_GROUP;
752
}
753
754
protected getOptions(): IChatEditorOptions {
755
return {};
756
}
757
}
758
759
export class OpenAgentSessionInNewWindowAction extends BaseOpenAgentSessionAction {
760
761
static readonly id = 'workbench.action.chat.openSessionInNewWindow';
762
763
constructor() {
764
super({
765
id: OpenAgentSessionInNewWindowAction.id,
766
title: localize2('chat.openSessionInNewWindow.label', "Open in New Window"),
767
menu: {
768
id: MenuId.AgentSessionsContext,
769
order: 3,
770
group: 'navigation'
771
}
772
});
773
}
774
775
protected getTargetGroup(): PreferredGroup {
776
return AUX_WINDOW_GROUP;
777
}
778
779
protected getOptions(): IChatEditorOptions {
780
return {
781
auxiliary: { compact: true, bounds: { width: 800, height: 640 } }
782
};
783
}
784
}
785
786
//#endregion
787
788
//#region Agent Sessions Sidebar
789
790
export class RefreshAgentSessionsViewerAction extends Action2 {
791
792
constructor() {
793
super({
794
id: 'agentSessionsViewer.refresh',
795
title: localize2('refresh', "Refresh Agent Sessions"),
796
icon: Codicon.refresh,
797
menu: {
798
id: MenuId.AgentSessionsToolbar,
799
group: 'navigation',
800
order: 1,
801
},
802
});
803
}
804
805
override run(accessor: ServicesAccessor, agentSessionsControl: IAgentSessionsControl) {
806
agentSessionsControl.refresh();
807
}
808
}
809
810
export class FindAgentSessionInViewerAction extends Action2 {
811
812
constructor() {
813
super({
814
id: 'agentSessionsViewer.find',
815
title: localize2('find', "Find Agent Session"),
816
icon: Codicon.search,
817
menu: {
818
id: MenuId.AgentSessionsToolbar,
819
group: 'navigation',
820
order: 2,
821
}
822
});
823
}
824
825
override run(accessor: ServicesAccessor, agentSessionsControl: IAgentSessionsControl) {
826
return agentSessionsControl.openFind();
827
}
828
}
829
830
abstract class UpdateChatViewWidthAction extends Action2 {
831
832
async run(accessor: ServicesAccessor): Promise<void> {
833
const layoutService = accessor.get(IWorkbenchLayoutService);
834
const viewDescriptorService = accessor.get(IViewDescriptorService);
835
const configurationService = accessor.get(IConfigurationService);
836
const viewsService = accessor.get(IViewsService);
837
const paneCompositeService = accessor.get(IPaneCompositePartService);
838
839
const chatLocation = viewDescriptorService.getViewLocationById(ChatViewId);
840
if (typeof chatLocation !== 'number') {
841
return; // we need a view location
842
}
843
844
// Determine if we can resize the view: this is not possible
845
// for when the chat view is in the panel at the top or bottom
846
const panelPosition = layoutService.getPanelPosition();
847
const canResizeView = chatLocation !== ViewContainerLocation.Panel || (panelPosition === Position.LEFT || panelPosition === Position.RIGHT);
848
849
// Update configuration if needed
850
const chatViewSessionsEnabled = configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsEnabled);
851
if (!chatViewSessionsEnabled) {
852
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true);
853
}
854
855
let chatView = viewsService.getActiveViewWithId<ChatViewPane>(ChatViewId);
856
if (!chatView) {
857
chatView = await viewsService.openView<ChatViewPane>(ChatViewId, false);
858
}
859
if (!chatView) {
860
return; // we need the chat view
861
}
862
863
const configuredOrientation = configurationService.getValue<'stacked' | 'sideBySide' | unknown>(ChatConfiguration.ChatViewSessionsOrientation);
864
let validatedConfiguredOrientation: 'stacked' | 'sideBySide';
865
if (configuredOrientation === 'stacked' || configuredOrientation === 'sideBySide') {
866
validatedConfiguredOrientation = configuredOrientation;
867
} else {
868
validatedConfiguredOrientation = 'sideBySide'; // default
869
}
870
871
const newOrientation = this.getOrientation();
872
const lastWidthForOrientation = chatView?.getLastDimensions(newOrientation)?.width;
873
874
if ((!canResizeView || validatedConfiguredOrientation === 'sideBySide') && newOrientation === AgentSessionsViewerOrientation.Stacked) {
875
chatView.updateConfiguredSessionsViewerOrientation('stacked');
876
} else if ((!canResizeView || validatedConfiguredOrientation === 'stacked') && newOrientation === AgentSessionsViewerOrientation.SideBySide) {
877
chatView.updateConfiguredSessionsViewerOrientation('sideBySide');
878
}
879
880
if (!canResizeView) {
881
return; // location does not allow for resize (panel top or bottom)
882
}
883
884
const part = paneCompositeService.getPartId(chatLocation);
885
let currentSize = layoutService.getSize(part);
886
887
const chatViewDefaultWidth = 300;
888
const sessionsViewDefaultWidth = chatViewDefaultWidth;
889
const sideBySideMinWidth = chatViewDefaultWidth + sessionsViewDefaultWidth + 1; // account for possible theme border
890
891
if (
892
(newOrientation === AgentSessionsViewerOrientation.SideBySide && currentSize.width >= sideBySideMinWidth) || // already wide enough to show side by side
893
(newOrientation === AgentSessionsViewerOrientation.Stacked && chatLocation === ViewContainerLocation.AuxiliaryBar && layoutService.isAuxiliaryBarMaximized()) // try to not leave maximized state if maximized
894
) {
895
return;
896
}
897
898
// Leave maximized state if applicable
899
if (chatLocation === ViewContainerLocation.AuxiliaryBar) {
900
layoutService.setAuxiliaryBarMaximized(false);
901
currentSize = layoutService.getSize(part);
902
}
903
904
// Figure out the right new width
905
let newWidth: number;
906
if (newOrientation === AgentSessionsViewerOrientation.SideBySide) {
907
newWidth = Math.max(sideBySideMinWidth, lastWidthForOrientation || Math.round(layoutService.mainContainerDimension.width / 2));
908
} else {
909
newWidth = lastWidthForOrientation || Math.max(chatViewDefaultWidth, currentSize.width - sessionsViewDefaultWidth);
910
}
911
912
// Apply the new width
913
layoutService.setSize(part, { width: newWidth, height: currentSize.height });
914
915
// If we figure out that the width was not applied due to constraints (such as window dimensions),
916
// we maximize the auxiliary bar to ensure the side by side experience is optimal
917
const actualSize = layoutService.getSize(part);
918
if (
919
chatLocation === ViewContainerLocation.AuxiliaryBar && // only applicable for auxiliary bar
920
newOrientation === AgentSessionsViewerOrientation.SideBySide && // only applicable when going to side by side
921
actualSize.width < sideBySideMinWidth // width is still not enough for side by side
922
) {
923
layoutService.setAuxiliaryBarMaximized(true);
924
}
925
}
926
927
abstract getOrientation(): AgentSessionsViewerOrientation;
928
}
929
930
export class ShowAgentSessionsSidebar extends UpdateChatViewWidthAction {
931
932
static readonly ID = 'agentSessions.showAgentSessionsSidebar';
933
static readonly TITLE = localize2('showAgentSessionsSidebar', "Show Agent Sessions Sidebar");
934
935
constructor() {
936
super({
937
id: ShowAgentSessionsSidebar.ID,
938
title: ShowAgentSessionsSidebar.TITLE,
939
precondition: ContextKeyExpr.and(
940
ChatContextKeys.enabled,
941
ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.Stacked),
942
),
943
f1: true,
944
category: AGENT_SESSIONS_CATEGORY,
945
});
946
}
947
948
override getOrientation(): AgentSessionsViewerOrientation {
949
return AgentSessionsViewerOrientation.SideBySide;
950
}
951
}
952
953
export class HideAgentSessionsSidebar extends UpdateChatViewWidthAction {
954
955
static readonly ID = 'agentSessions.hideAgentSessionsSidebar';
956
static readonly TITLE = localize2('hideAgentSessionsSidebar', "Hide Agent Sessions Sidebar");
957
958
constructor() {
959
super({
960
id: HideAgentSessionsSidebar.ID,
961
title: HideAgentSessionsSidebar.TITLE,
962
precondition: ContextKeyExpr.and(
963
ChatContextKeys.enabled,
964
ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.SideBySide),
965
),
966
f1: true,
967
category: AGENT_SESSIONS_CATEGORY,
968
});
969
}
970
971
override getOrientation(): AgentSessionsViewerOrientation {
972
return AgentSessionsViewerOrientation.Stacked;
973
}
974
}
975
976
export class ToggleAgentSessionsSidebar extends Action2 {
977
978
static readonly ID = 'agentSessions.toggleAgentSessionsSidebar';
979
static readonly TITLE = localize2('toggleAgentSessionsSidebar', "Toggle Agent Sessions Sidebar");
980
981
constructor() {
982
super({
983
id: ToggleAgentSessionsSidebar.ID,
984
title: ToggleAgentSessionsSidebar.TITLE,
985
precondition: ChatContextKeys.enabled,
986
f1: true,
987
category: AGENT_SESSIONS_CATEGORY,
988
});
989
}
990
991
async run(accessor: ServicesAccessor): Promise<void> {
992
const commandService = accessor.get(ICommandService);
993
const viewsService = accessor.get(IViewsService);
994
995
const chatView = viewsService.getActiveViewWithId<ChatViewPane>(ChatViewId);
996
const currentOrientation = chatView?.getSessionsViewerOrientation();
997
998
if (currentOrientation === AgentSessionsViewerOrientation.SideBySide) {
999
await commandService.executeCommand(HideAgentSessionsSidebar.ID);
1000
} else {
1001
await commandService.executeCommand(ShowAgentSessionsSidebar.ID);
1002
}
1003
}
1004
}
1005
1006
export class FocusAgentSessionsAction extends Action2 {
1007
1008
static readonly id = 'workbench.action.chat.focusAgentSessionsViewer';
1009
1010
constructor() {
1011
super({
1012
id: FocusAgentSessionsAction.id,
1013
title: localize2('chat.focusAgentSessionsViewer.label', "Focus Agent Sessions"),
1014
precondition: ContextKeyExpr.and(
1015
ChatContextKeys.enabled,
1016
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true)
1017
),
1018
category: AGENT_SESSIONS_CATEGORY,
1019
f1: true,
1020
});
1021
}
1022
1023
async run(accessor: ServicesAccessor): Promise<void> {
1024
const viewsService = accessor.get(IViewsService);
1025
const configurationService = accessor.get(IConfigurationService);
1026
const commandService = accessor.get(ICommandService);
1027
1028
const chatView = await viewsService.openView<ChatViewPane>(ChatViewId, true);
1029
const focused = chatView?.focusSessions();
1030
if (focused) {
1031
return;
1032
}
1033
1034
const configuredSessionsViewerOrientation = configurationService.getValue<'stacked' | 'sideBySide' | unknown>(ChatConfiguration.ChatViewSessionsOrientation);
1035
if (configuredSessionsViewerOrientation === 'stacked') {
1036
await commandService.executeCommand(ACTION_ID_NEW_CHAT);
1037
} else {
1038
await commandService.executeCommand(ShowAgentSessionsSidebar.ID);
1039
}
1040
1041
chatView?.focusSessions();
1042
}
1043
}
1044
1045
//#endregion
1046
1047