Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts
5267 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 { Codicon } from '../../../../../base/common/codicons.js';
7
import { hash } from '../../../../../base/common/hash.js';
8
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
9
import { basename } from '../../../../../base/common/resources.js';
10
import { ThemeIcon } from '../../../../../base/common/themables.js';
11
import { assertType } from '../../../../../base/common/types.js';
12
import { URI } from '../../../../../base/common/uri.js';
13
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
14
import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';
15
import { localize, localize2 } from '../../../../../nls.js';
16
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
17
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
18
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
19
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
20
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
21
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
22
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
23
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
24
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
25
import { IChatMode, IChatModeService } from '../../common/chatModes.js';
26
import { chatVariableLeader } from '../../common/requestParser/chatParserTypes.js';
27
import { IChatService } from '../../common/chatService/chatService.js';
28
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js';
29
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
30
import { ILanguageModelToolsService } from '../../common/tools/languageModelToolsService.js';
31
import { PromptsStorage } from '../../common/promptSyntax/service/promptsService.js';
32
import { IChatSessionsService } from '../../common/chatSessionsService.js';
33
import { IChatWidget, IChatWidgetService } from '../chat.js';
34
import { getAgentSessionProvider, AgentSessionProviders } from '../agentSessions/agentSessions.js';
35
import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js';
36
import { ctxHasEditorModification, ctxHasRequestInProgress, ctxIsGlobalEditingSession } from '../chatEditing/chatEditingEditorContextKeys.js';
37
import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, handleCurrentEditingSession, handleModeSwitch } from './chatActions.js';
38
import { CreateRemoteAgentJobAction } from './chatContinueInAction.js';
39
40
export interface IVoiceChatExecuteActionContext {
41
readonly disableTimeout?: boolean;
42
}
43
44
export interface IChatExecuteActionContext {
45
widget?: IChatWidget;
46
inputValue?: string;
47
voice?: IVoiceChatExecuteActionContext;
48
}
49
50
abstract class SubmitAction extends Action2 {
51
async run(accessor: ServicesAccessor, ...args: unknown[]) {
52
const context = args[0] as IChatExecuteActionContext | undefined;
53
const telemetryService = accessor.get(ITelemetryService);
54
const widgetService = accessor.get(IChatWidgetService);
55
const widget = context?.widget ?? widgetService.lastFocusedWidget;
56
57
// Check if there's a pending delegation target
58
const pendingDelegationTarget = widget?.input.pendingDelegationTarget;
59
if (pendingDelegationTarget && pendingDelegationTarget !== AgentSessionProviders.Local) {
60
return await this.handleDelegation(accessor, widget, pendingDelegationTarget);
61
}
62
63
if (widget?.viewModel?.editing) {
64
const configurationService = accessor.get(IConfigurationService);
65
const dialogService = accessor.get(IDialogService);
66
const chatService = accessor.get(IChatService);
67
const chatModel = chatService.getSession(widget.viewModel.sessionResource);
68
if (!chatModel) {
69
return;
70
}
71
72
const session = chatModel.editingSession;
73
if (!session) {
74
return;
75
}
76
77
const requestId = widget.viewModel?.editing.id;
78
79
if (requestId) {
80
const chatRequests = chatModel.getRequests();
81
const itemIndex = chatRequests.findIndex(request => request.id === requestId);
82
const editsToUndo = chatRequests.length - itemIndex;
83
84
const requestsToRemove = chatRequests.slice(itemIndex);
85
const requestIdsToRemove = new Set(requestsToRemove.map(request => request.id));
86
const entriesModifiedInRequestsToRemove = session.entries.get().filter((entry) => requestIdsToRemove.has(entry.lastModifyingRequestId)) ?? [];
87
const shouldPrompt = entriesModifiedInRequestsToRemove.length > 0 && configurationService.getValue('chat.editing.confirmEditRequestRemoval') === true;
88
89
let message: string;
90
if (editsToUndo === 1) {
91
if (entriesModifiedInRequestsToRemove.length === 1) {
92
message = localize('chat.removeLast.confirmation.message2', "This will remove your last request and undo the edits made to {0}. Do you want to proceed?", basename(entriesModifiedInRequestsToRemove[0].modifiedURI));
93
} else {
94
message = localize('chat.removeLast.confirmation.multipleEdits.message', "This will remove your last request and undo edits made to {0} files in your working set. Do you want to proceed?", entriesModifiedInRequestsToRemove.length);
95
}
96
} else {
97
if (entriesModifiedInRequestsToRemove.length === 1) {
98
message = localize('chat.remove.confirmation.message2', "This will remove all subsequent requests and undo edits made to {0}. Do you want to proceed?", basename(entriesModifiedInRequestsToRemove[0].modifiedURI));
99
} else {
100
message = localize('chat.remove.confirmation.multipleEdits.message', "This will remove all subsequent requests and undo edits made to {0} files in your working set. Do you want to proceed?", entriesModifiedInRequestsToRemove.length);
101
}
102
}
103
104
const confirmation = shouldPrompt
105
? await dialogService.confirm({
106
title: editsToUndo === 1
107
? localize('chat.removeLast.confirmation.title', "Do you want to undo your last edit?")
108
: localize('chat.remove.confirmation.title', "Do you want to undo {0} edits?", editsToUndo),
109
message: message,
110
primaryButton: localize('chat.remove.confirmation.primaryButton', "Yes"),
111
checkbox: { label: localize('chat.remove.confirmation.checkbox', "Don't ask again"), checked: false },
112
type: 'info'
113
})
114
: { confirmed: true };
115
116
type EditUndoEvent = {
117
editRequestType: string;
118
outcome: 'cancelled' | 'applied';
119
editsUndoCount: number;
120
};
121
122
type EditUndoEventClassification = {
123
owner: 'justschen';
124
comment: 'Event used to gain insights into when there are pending changes to undo, and whether edited requests are applied or cancelled.';
125
editRequestType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Current entry point for editing a request.' };
126
outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the edit was cancelled or applied.' };
127
editsUndoCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of edits that would be undone.'; 'isMeasurement': true };
128
};
129
130
if (!confirmation.confirmed) {
131
telemetryService.publicLog2<EditUndoEvent, EditUndoEventClassification>('chat.undoEditsConfirmation', {
132
editRequestType: configurationService.getValue<string>('chat.editRequests'),
133
outcome: 'cancelled',
134
editsUndoCount: editsToUndo
135
});
136
return;
137
} else if (editsToUndo > 0) {
138
telemetryService.publicLog2<EditUndoEvent, EditUndoEventClassification>('chat.undoEditsConfirmation', {
139
editRequestType: configurationService.getValue<string>('chat.editRequests'),
140
outcome: 'applied',
141
editsUndoCount: editsToUndo
142
});
143
}
144
145
if (confirmation.checkboxChecked) {
146
await configurationService.updateValue('chat.editing.confirmEditRequestRemoval', false);
147
}
148
149
// Restore the snapshot to what it was before the request(s) that we deleted
150
const snapshotRequestId = chatRequests[itemIndex].id;
151
await session.restoreSnapshot(snapshotRequestId, undefined);
152
}
153
} else if (widget?.viewModel?.model.checkpoint) {
154
widget.viewModel.model.setCheckpoint(undefined);
155
}
156
widget?.acceptInput(context?.inputValue);
157
}
158
159
private async handleDelegation(accessor: ServicesAccessor, widget: IChatWidget, delegationTarget: Exclude<AgentSessionProviders, AgentSessionProviders.Local>): Promise<void> {
160
const chatSessionsService = accessor.get(IChatSessionsService);
161
162
// Find the contribution for the delegation target
163
const contributions = chatSessionsService.getAllChatSessionContributions();
164
const targetContribution = contributions.find(contrib => {
165
const providerType = getAgentSessionProvider(contrib.type);
166
return providerType === delegationTarget;
167
});
168
169
if (!targetContribution) {
170
throw new Error(`No contribution found for delegation target: ${delegationTarget}`);
171
}
172
173
if (targetContribution.canDelegate === false) {
174
throw new Error(`The contribution for delegation target: ${delegationTarget} does not support delegation.`);
175
}
176
177
return new CreateRemoteAgentJobAction().run(accessor, targetContribution, widget);
178
}
179
}
180
181
const requestInProgressOrPendingToolCall = ContextKeyExpr.or(ChatContextKeys.requestInProgress, ChatContextKeys.Editing.hasToolConfirmation);
182
const whenNotInProgress = ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.Editing.hasToolConfirmation.negate());
183
184
export class ChatSubmitAction extends SubmitAction {
185
static readonly ID = 'workbench.action.chat.submit';
186
187
constructor() {
188
const menuCondition = ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask);
189
const precondition = ContextKeyExpr.and(
190
ChatContextKeys.inputHasText,
191
whenNotInProgress,
192
ChatContextKeys.chatSessionOptionsValid,
193
);
194
195
super({
196
id: ChatSubmitAction.ID,
197
title: localize2('interactive.submit.label', "Send"),
198
f1: false,
199
category: CHAT_CATEGORY,
200
icon: Codicon.send,
201
precondition,
202
toggled: {
203
condition: ChatContextKeys.lockedToCodingAgent,
204
icon: Codicon.send,
205
tooltip: localize('sendToAgent', "Send to Agent"),
206
},
207
keybinding: {
208
when: ContextKeyExpr.and(
209
ChatContextKeys.inChatInput,
210
ChatContextKeys.withinEditSessionDiff.negate(),
211
),
212
primary: KeyCode.Enter,
213
weight: KeybindingWeight.EditorContrib
214
},
215
menu: [
216
{
217
id: MenuId.ChatExecute,
218
order: 4,
219
when: ContextKeyExpr.and(
220
whenNotInProgress,
221
menuCondition,
222
ChatContextKeys.withinEditSessionDiff.negate(),
223
),
224
group: 'navigation',
225
alt: {
226
id: 'workbench.action.chat.sendToNewChat',
227
title: localize2('chat.newChat.label', "Send to New Chat"),
228
icon: Codicon.plus
229
}
230
}, {
231
id: MenuId.ChatEditorInlineExecute,
232
group: 'navigation',
233
order: 4,
234
when: ContextKeyExpr.and(
235
ContextKeyExpr.or(ctxHasEditorModification.negate(), ChatContextKeys.inputHasText),
236
whenNotInProgress,
237
ChatContextKeys.requestInProgress.negate(),
238
menuCondition
239
),
240
}]
241
});
242
}
243
}
244
245
246
export const ToggleAgentModeActionId = 'workbench.action.chat.toggleAgentMode';
247
248
export interface IToggleChatModeArgs {
249
modeId: ChatModeKind | string;
250
sessionResource: URI | undefined;
251
}
252
253
type ChatModeChangeClassification = {
254
owner: 'digitarald';
255
comment: 'Reporting when agent is switched between different modes';
256
fromMode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The previous agent name' };
257
mode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The new agent name' };
258
requestCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of requests in the current chat session'; 'isMeasurement': true };
259
storage?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Source of the target mode (builtin, local, user, extension)' };
260
extensionId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension ID if the target mode is from an extension' };
261
toolsCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of custom tools in the target mode'; 'isMeasurement': true };
262
handoffsCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of handoffs in the target mode'; 'isMeasurement': true };
263
};
264
265
type ChatModeChangeEvent = {
266
fromMode: string;
267
mode: string;
268
requestCount: number;
269
storage?: string;
270
extensionId?: string;
271
toolsCount?: number;
272
handoffsCount?: number;
273
};
274
275
class ToggleChatModeAction extends Action2 {
276
277
static readonly ID = ToggleAgentModeActionId;
278
279
constructor() {
280
super({
281
id: ToggleChatModeAction.ID,
282
title: localize2('interactive.toggleAgent.label', "Switch to Next Agent"),
283
f1: true,
284
category: CHAT_CATEGORY,
285
precondition: ContextKeyExpr.and(
286
ChatContextKeys.enabled,
287
ChatContextKeys.requestInProgress.negate())
288
});
289
}
290
291
async run(accessor: ServicesAccessor, ...args: unknown[]) {
292
const commandService = accessor.get(ICommandService);
293
const configurationService = accessor.get(IConfigurationService);
294
const instaService = accessor.get(IInstantiationService);
295
const modeService = accessor.get(IChatModeService);
296
const telemetryService = accessor.get(ITelemetryService);
297
const chatWidgetService = accessor.get(IChatWidgetService);
298
299
const arg = args.at(0) as IToggleChatModeArgs | undefined;
300
let widget: IChatWidget | undefined;
301
if (arg?.sessionResource) {
302
widget = chatWidgetService.getWidgetBySessionResource(arg.sessionResource);
303
} else {
304
widget = getEditingSessionContext(accessor, args)?.chatWidget;
305
}
306
307
if (!widget) {
308
return;
309
}
310
311
const chatSession = widget.viewModel?.model;
312
const requestCount = chatSession?.getRequests().length ?? 0;
313
const switchToMode = (arg && (modeService.findModeById(arg.modeId) || modeService.findModeByName(arg.modeId))) ?? this.getNextMode(widget, requestCount, configurationService, modeService);
314
315
const currentMode = widget.input.currentModeObs.get();
316
if (switchToMode.id === currentMode.id) {
317
return;
318
}
319
320
const chatModeCheck = await instaService.invokeFunction(handleModeSwitch, widget.input.currentModeKind, switchToMode.kind, requestCount, widget.viewModel?.model);
321
if (!chatModeCheck) {
322
return;
323
}
324
325
// Send telemetry for mode change
326
const storage = switchToMode.source?.storage ?? 'builtin';
327
const extensionId = switchToMode.source?.storage === 'extension' ? switchToMode.source.extensionId.value : undefined;
328
const toolsCount = switchToMode.customTools?.get()?.length ?? 0;
329
const handoffsCount = switchToMode.handOffs?.get()?.length ?? 0;
330
331
// Hash names for user/workspace modes to only instrument non-user agent names
332
const getModeNameForTelemetry = (mode: IChatMode): string => {
333
const modeStorage = mode.source?.storage;
334
if (modeStorage === PromptsStorage.local || modeStorage === PromptsStorage.user) {
335
return String(hash(mode.name.get()));
336
}
337
return mode.name.get();
338
};
339
340
telemetryService.publicLog2<ChatModeChangeEvent, ChatModeChangeClassification>('chat.modeChange', {
341
fromMode: getModeNameForTelemetry(currentMode),
342
mode: getModeNameForTelemetry(switchToMode),
343
requestCount: requestCount,
344
storage,
345
extensionId,
346
toolsCount,
347
handoffsCount
348
});
349
350
widget.input.setChatMode(switchToMode.id);
351
352
if (chatModeCheck.needToClearSession) {
353
await commandService.executeCommand(ACTION_ID_NEW_CHAT);
354
}
355
}
356
357
private getNextMode(chatWidget: IChatWidget, requestCount: number, configurationService: IConfigurationService, modeService: IChatModeService): IChatMode {
358
const modes = modeService.getModes();
359
const flat = [
360
...modes.builtin.filter(mode => {
361
return mode.kind !== ChatModeKind.Edit || configurationService.getValue(ChatConfiguration.Edits2Enabled) || requestCount === 0;
362
}),
363
...(modes.custom ?? []),
364
];
365
366
const curModeIndex = flat.findIndex(mode => mode.id === chatWidget.input.currentModeObs.get().id);
367
const newMode = flat[(curModeIndex + 1) % flat.length];
368
return newMode;
369
}
370
}
371
372
class SwitchToNextModelAction extends Action2 {
373
static readonly ID = 'workbench.action.chat.switchToNextModel';
374
375
constructor() {
376
super({
377
id: SwitchToNextModelAction.ID,
378
title: localize2('interactive.switchToNextModel.label', "Switch to Next Model"),
379
category: CHAT_CATEGORY,
380
f1: true,
381
precondition: ChatContextKeys.enabled,
382
});
383
}
384
385
override run(accessor: ServicesAccessor, ...args: unknown[]): void {
386
const widgetService = accessor.get(IChatWidgetService);
387
const widget = widgetService.lastFocusedWidget;
388
widget?.input.switchToNextModel();
389
}
390
}
391
392
export class OpenModelPickerAction extends Action2 {
393
static readonly ID = 'workbench.action.chat.openModelPicker';
394
395
constructor() {
396
super({
397
id: OpenModelPickerAction.ID,
398
title: localize2('interactive.openModelPicker.label', "Open Model Picker"),
399
category: CHAT_CATEGORY,
400
f1: false,
401
keybinding: {
402
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Period,
403
weight: KeybindingWeight.WorkbenchContrib,
404
when: ChatContextKeys.inChatInput
405
},
406
precondition: ChatContextKeys.enabled,
407
menu: {
408
id: MenuId.ChatInput,
409
order: 3,
410
group: 'navigation',
411
when:
412
ContextKeyExpr.and(
413
ChatContextKeys.lockedToCodingAgent.negate(),
414
ContextKeyExpr.or(
415
ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat),
416
ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditorInline),
417
ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Notebook),
418
ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Terminal)),
419
// Hide in welcome view when session type is not local
420
ContextKeyExpr.or(
421
ChatContextKeys.inAgentSessionsWelcome.negate(),
422
ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local))
423
)
424
}
425
});
426
}
427
428
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
429
const widgetService = accessor.get(IChatWidgetService);
430
const widget = widgetService.lastFocusedWidget;
431
if (widget) {
432
await widgetService.reveal(widget);
433
widget.input.openModelPicker();
434
}
435
}
436
}
437
export class OpenModePickerAction extends Action2 {
438
static readonly ID = 'workbench.action.chat.openModePicker';
439
440
constructor() {
441
super({
442
id: OpenModePickerAction.ID,
443
title: localize2('interactive.openModePicker.label', "Open Agent Picker"),
444
tooltip: localize('setChatMode', "Set Agent"),
445
category: CHAT_CATEGORY,
446
f1: false,
447
precondition: ChatContextKeys.enabled,
448
keybinding: {
449
when: ContextKeyExpr.and(
450
ChatContextKeys.inChatInput,
451
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)),
452
primary: KeyMod.CtrlCmd | KeyCode.Period,
453
weight: KeybindingWeight.EditorContrib
454
},
455
menu: [
456
{
457
id: MenuId.ChatInput,
458
order: 1,
459
when: ContextKeyExpr.and(
460
ChatContextKeys.enabled,
461
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
462
ChatContextKeys.inQuickChat.negate(),
463
ContextKeyExpr.or(
464
ChatContextKeys.lockedToCodingAgent.negate(),
465
ChatContextKeys.chatSessionHasCustomAgentTarget),
466
// Hide in welcome view when session type is not local
467
ContextKeyExpr.or(
468
ChatContextKeys.inAgentSessionsWelcome.negate(),
469
ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local))),
470
group: 'navigation',
471
},
472
]
473
});
474
}
475
476
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
477
const widgetService = accessor.get(IChatWidgetService);
478
const widget = widgetService.lastFocusedWidget;
479
if (widget) {
480
widget.input.openModePicker();
481
}
482
}
483
}
484
485
export class OpenSessionTargetPickerAction extends Action2 {
486
static readonly ID = 'workbench.action.chat.openSessionTargetPicker';
487
488
constructor() {
489
super({
490
id: OpenSessionTargetPickerAction.ID,
491
title: localize2('interactive.openSessionTargetPicker.label', "Open Session Target Picker"),
492
tooltip: localize('setSessionTarget', "Set Session Target"),
493
category: CHAT_CATEGORY,
494
f1: false,
495
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.or(ChatContextKeys.chatSessionIsEmpty, ChatContextKeys.inAgentSessionsWelcome), ChatContextKeys.currentlyEditingInput.negate(), ChatContextKeys.currentlyEditing.negate()),
496
menu: [
497
{
498
id: MenuId.ChatInput,
499
order: 0,
500
when: ContextKeyExpr.and(
501
ChatContextKeys.enabled,
502
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
503
ChatContextKeys.inQuickChat.negate(),
504
ChatContextKeys.chatSessionIsEmpty),
505
group: 'navigation',
506
},
507
]
508
});
509
}
510
511
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
512
const widgetService = accessor.get(IChatWidgetService);
513
const widget = widgetService.lastFocusedWidget;
514
if (widget) {
515
widget.input.openSessionTargetPicker();
516
}
517
}
518
}
519
520
export class OpenDelegationPickerAction extends Action2 {
521
static readonly ID = 'workbench.action.chat.openDelegationPicker';
522
523
constructor() {
524
super({
525
id: OpenDelegationPickerAction.ID,
526
title: localize2('interactive.openDelegationPicker.label', "Open Delegation Picker"),
527
tooltip: localize('delegateSession', "Delegate Session"),
528
category: CHAT_CATEGORY,
529
f1: false,
530
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.chatSessionIsEmpty.negate(), ChatContextKeys.currentlyEditingInput.negate(), ChatContextKeys.currentlyEditing.negate()),
531
menu: [
532
{
533
id: MenuId.ChatInput,
534
order: 0.5,
535
when: ContextKeyExpr.and(
536
ChatContextKeys.enabled,
537
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
538
ChatContextKeys.inQuickChat.negate(),
539
ChatContextKeys.chatSessionIsEmpty.negate()),
540
group: 'navigation',
541
},
542
]
543
});
544
}
545
546
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
547
const widgetService = accessor.get(IChatWidgetService);
548
const widget = widgetService.lastFocusedWidget;
549
if (widget) {
550
widget.input.openDelegationPicker();
551
}
552
}
553
}
554
555
export class OpenWorkspacePickerAction extends Action2 {
556
static readonly ID = 'workbench.action.chat.openWorkspacePicker';
557
558
constructor() {
559
super({
560
id: OpenWorkspacePickerAction.ID,
561
title: localize2('interactive.openWorkspacePicker.label', "Open Workspace Picker"),
562
tooltip: localize('selectWorkspace', "Select Target Workspace"),
563
category: CHAT_CATEGORY,
564
f1: false,
565
precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.inAgentSessionsWelcome),
566
menu: [
567
{
568
id: MenuId.ChatInput,
569
order: 0.6,
570
when: ContextKeyExpr.and(
571
ChatContextKeys.inAgentSessionsWelcome,
572
ChatContextKeys.chatSessionType.isEqualTo('local')
573
),
574
group: 'navigation',
575
},
576
]
577
});
578
}
579
580
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
581
// The picker is opened via the action view item
582
}
583
}
584
585
export class ChatSessionPrimaryPickerAction extends Action2 {
586
static readonly ID = 'workbench.action.chat.chatSessionPrimaryPicker';
587
constructor() {
588
super({
589
id: ChatSessionPrimaryPickerAction.ID,
590
title: localize2('interactive.openChatSessionPrimaryPicker.label', "Open Model Picker"),
591
category: CHAT_CATEGORY,
592
f1: false,
593
precondition: ChatContextKeys.enabled,
594
menu: {
595
id: MenuId.ChatInput,
596
order: 4,
597
group: 'navigation',
598
when:
599
ContextKeyExpr.and(
600
ChatContextKeys.chatSessionHasModels,
601
ContextKeyExpr.or(
602
ChatContextKeys.lockedToCodingAgent,
603
ContextKeyExpr.and(
604
ChatContextKeys.inAgentSessionsWelcome,
605
ChatContextKeys.chatSessionType.notEqualsTo('local')
606
)
607
)
608
)
609
}
610
});
611
}
612
613
override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {
614
const widgetService = accessor.get(IChatWidgetService);
615
const widget = widgetService.lastFocusedWidget;
616
if (widget) {
617
widget.input.openChatSessionPicker();
618
}
619
}
620
}
621
622
export const ChangeChatModelActionId = 'workbench.action.chat.changeModel';
623
class ChangeChatModelAction extends Action2 {
624
static readonly ID = ChangeChatModelActionId;
625
626
constructor() {
627
super({
628
id: ChangeChatModelAction.ID,
629
title: localize2('interactive.changeModel.label', "Change Model"),
630
category: CHAT_CATEGORY,
631
f1: false,
632
precondition: ChatContextKeys.enabled,
633
});
634
}
635
636
override run(accessor: ServicesAccessor, ...args: unknown[]): void {
637
const modelInfo = args[0] as Pick<ILanguageModelChatMetadata, 'vendor' | 'id' | 'family'>;
638
// Type check the arg
639
assertType(typeof modelInfo.vendor === 'string' && typeof modelInfo.id === 'string' && typeof modelInfo.family === 'string');
640
const widgetService = accessor.get(IChatWidgetService);
641
const widgets = widgetService.getAllWidgets();
642
for (const widget of widgets) {
643
widget.input.switchModel(modelInfo);
644
}
645
}
646
}
647
648
export class ChatEditingSessionSubmitAction extends SubmitAction {
649
static readonly ID = 'workbench.action.edits.submit';
650
651
constructor() {
652
const notInProgressOrEditing = ContextKeyExpr.and(
653
ContextKeyExpr.or(whenNotInProgress, ChatContextKeys.editingRequestType.isEqualTo(ChatContextKeys.EditingRequestType.Sent)),
654
ChatContextKeys.editingRequestType.notEqualsTo(ChatContextKeys.EditingRequestType.QueueOrSteer)
655
);
656
657
const menuCondition = ChatContextKeys.chatModeKind.notEqualsTo(ChatModeKind.Ask);
658
const precondition = ContextKeyExpr.and(
659
ChatContextKeys.inputHasText,
660
notInProgressOrEditing,
661
ChatContextKeys.chatSessionOptionsValid
662
);
663
664
super({
665
id: ChatEditingSessionSubmitAction.ID,
666
title: localize2('edits.submit.label', "Send"),
667
f1: false,
668
category: CHAT_CATEGORY,
669
icon: Codicon.send,
670
precondition,
671
menu: [
672
{
673
id: MenuId.ChatExecute,
674
order: 4,
675
when: ContextKeyExpr.and(
676
notInProgressOrEditing,
677
menuCondition),
678
group: 'navigation',
679
alt: {
680
id: 'workbench.action.chat.sendToNewChat',
681
title: localize2('chat.newChat.label', "Send to New Chat"),
682
icon: Codicon.plus
683
}
684
}]
685
});
686
}
687
}
688
689
class SubmitWithoutDispatchingAction extends Action2 {
690
static readonly ID = 'workbench.action.chat.submitWithoutDispatching';
691
692
constructor() {
693
const precondition = ContextKeyExpr.and(
694
ChatContextKeys.inputHasText,
695
whenNotInProgress,
696
ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask),
697
);
698
699
super({
700
id: SubmitWithoutDispatchingAction.ID,
701
title: localize2('interactive.submitWithoutDispatch.label', "Send"),
702
f1: false,
703
category: CHAT_CATEGORY,
704
precondition,
705
keybinding: {
706
when: ChatContextKeys.inChatInput,
707
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter,
708
weight: KeybindingWeight.EditorContrib
709
}
710
});
711
}
712
713
run(accessor: ServicesAccessor, ...args: unknown[]) {
714
const context = args[0] as IChatExecuteActionContext | undefined;
715
716
const widgetService = accessor.get(IChatWidgetService);
717
const widget = context?.widget ?? widgetService.lastFocusedWidget;
718
widget?.acceptInput(context?.inputValue, { noCommandDetection: true });
719
}
720
}
721
722
export class ChatSubmitWithCodebaseAction extends Action2 {
723
static readonly ID = 'workbench.action.chat.submitWithCodebase';
724
725
constructor() {
726
const precondition = ContextKeyExpr.and(
727
ChatContextKeys.inputHasText,
728
whenNotInProgress,
729
);
730
731
super({
732
id: ChatSubmitWithCodebaseAction.ID,
733
title: localize2('actions.chat.submitWithCodebase', "Send with {0}", `${chatVariableLeader}codebase`),
734
precondition,
735
keybinding: {
736
when: ChatContextKeys.inChatInput,
737
primary: KeyMod.CtrlCmd | KeyCode.Enter,
738
weight: KeybindingWeight.EditorContrib
739
},
740
});
741
}
742
743
run(accessor: ServicesAccessor, ...args: unknown[]) {
744
const context = args[0] as IChatExecuteActionContext | undefined;
745
746
const widgetService = accessor.get(IChatWidgetService);
747
const widget = context?.widget ?? widgetService.lastFocusedWidget;
748
if (!widget) {
749
return;
750
}
751
752
const languageModelToolsService = accessor.get(ILanguageModelToolsService);
753
const codebaseTool = languageModelToolsService.getToolByName('codebase');
754
if (!codebaseTool) {
755
return;
756
}
757
758
widget.input.attachmentModel.addContext({
759
id: codebaseTool.id,
760
name: codebaseTool.displayName ?? '',
761
fullName: codebaseTool.displayName ?? '',
762
value: undefined,
763
icon: ThemeIcon.isThemeIcon(codebaseTool.icon) ? codebaseTool.icon : undefined,
764
kind: 'tool'
765
});
766
widget.acceptInput();
767
}
768
}
769
770
class SendToNewChatAction extends Action2 {
771
constructor() {
772
const precondition = ChatContextKeys.inputHasText;
773
774
super({
775
id: 'workbench.action.chat.sendToNewChat',
776
title: localize2('chat.newChat.label', "Send to New Chat"),
777
precondition,
778
category: CHAT_CATEGORY,
779
f1: false,
780
keybinding: {
781
weight: KeybindingWeight.WorkbenchContrib,
782
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
783
when: ChatContextKeys.inChatInput,
784
}
785
});
786
}
787
788
async run(accessor: ServicesAccessor, ...args: unknown[]) {
789
const context = args[0] as IChatExecuteActionContext | undefined;
790
791
const widgetService = accessor.get(IChatWidgetService);
792
const dialogService = accessor.get(IDialogService);
793
const chatService = accessor.get(IChatService);
794
const widget = context?.widget ?? widgetService.lastFocusedWidget;
795
if (!widget) {
796
return;
797
}
798
799
const inputBeforeClear = widget.getInput();
800
801
// Cancel any in-progress request before clearing
802
if (widget.viewModel) {
803
chatService.cancelCurrentRequestForSession(widget.viewModel.sessionResource);
804
}
805
806
if (widget.viewModel?.model) {
807
if (!(await handleCurrentEditingSession(widget.viewModel.model, undefined, dialogService))) {
808
return;
809
}
810
}
811
812
// Clear the input from the current session before creating a new one
813
widget.setInput('');
814
815
await widget.clear();
816
widget.acceptInput(inputBeforeClear, { storeToHistory: true });
817
}
818
}
819
820
export const CancelChatActionId = 'workbench.action.chat.cancel';
821
export class CancelAction extends Action2 {
822
static readonly ID = CancelChatActionId;
823
constructor() {
824
super({
825
id: CancelAction.ID,
826
title: localize2('interactive.cancel.label', "Cancel"),
827
f1: false,
828
category: CHAT_CATEGORY,
829
icon: Codicon.stopCircle,
830
menu: [{
831
id: MenuId.ChatExecute,
832
when: ContextKeyExpr.and(
833
requestInProgressOrPendingToolCall,
834
ChatContextKeys.remoteJobCreating.negate(),
835
ChatContextKeys.currentlyEditing.negate(),
836
),
837
order: 4,
838
group: 'navigation',
839
}, {
840
id: MenuId.ChatEditorInlineExecute,
841
when: ContextKeyExpr.and(
842
ctxIsGlobalEditingSession.negate(),
843
ctxHasRequestInProgress,
844
),
845
order: 4,
846
group: 'navigation',
847
}
848
],
849
keybinding: {
850
weight: KeybindingWeight.WorkbenchContrib,
851
primary: KeyMod.CtrlCmd | KeyCode.Escape,
852
when: ContextKeyExpr.and(
853
requestInProgressOrPendingToolCall,
854
ChatContextKeys.remoteJobCreating.negate()
855
),
856
win: { primary: KeyMod.Alt | KeyCode.Backspace },
857
}
858
});
859
}
860
861
run(accessor: ServicesAccessor, ...args: unknown[]) {
862
const context = args[0] as IChatExecuteActionContext | undefined;
863
const widgetService = accessor.get(IChatWidgetService);
864
const widget = context?.widget ?? widgetService.lastFocusedWidget;
865
if (!widget) {
866
return;
867
}
868
869
const chatService = accessor.get(IChatService);
870
if (widget.viewModel) {
871
chatService.cancelCurrentRequestForSession(widget.viewModel.sessionResource);
872
}
873
}
874
}
875
876
export const CancelChatEditId = 'workbench.edit.chat.cancel';
877
export class CancelEdit extends Action2 {
878
static readonly ID = CancelChatEditId;
879
constructor() {
880
super({
881
id: CancelEdit.ID,
882
title: localize2('interactive.cancelEdit.label', "Cancel Edit"),
883
f1: false,
884
category: CHAT_CATEGORY,
885
icon: Codicon.x,
886
menu: [
887
{
888
id: MenuId.ChatMessageTitle,
889
group: 'navigation',
890
order: 1,
891
when: ContextKeyExpr.and(ChatContextKeys.isRequest, ChatContextKeys.currentlyEditing, ContextKeyExpr.equals(`config.${ChatConfiguration.EditRequests}`, 'input'))
892
}
893
],
894
keybinding: {
895
primary: KeyCode.Escape,
896
when: ContextKeyExpr.and(ChatContextKeys.inChatInput,
897
EditorContextKeys.hoverVisible.toNegated(),
898
EditorContextKeys.hasNonEmptySelection.toNegated(),
899
EditorContextKeys.hasMultipleSelections.toNegated(),
900
ContextKeyExpr.or(ChatContextKeys.currentlyEditing, ChatContextKeys.currentlyEditingInput)),
901
weight: KeybindingWeight.EditorContrib - 5
902
}
903
});
904
}
905
906
run(accessor: ServicesAccessor, ...args: unknown[]) {
907
const context = args[0] as IChatExecuteActionContext | undefined;
908
909
const widgetService = accessor.get(IChatWidgetService);
910
const widget = context?.widget ?? widgetService.lastFocusedWidget;
911
if (!widget) {
912
return;
913
}
914
widget.finishedEditing();
915
}
916
}
917
918
919
export function registerChatExecuteActions() {
920
registerAction2(ChatSubmitAction);
921
registerAction2(ChatEditingSessionSubmitAction);
922
registerAction2(SubmitWithoutDispatchingAction);
923
registerAction2(CancelAction);
924
registerAction2(SendToNewChatAction);
925
registerAction2(ChatSubmitWithCodebaseAction);
926
registerAction2(ToggleChatModeAction);
927
registerAction2(SwitchToNextModelAction);
928
registerAction2(OpenModelPickerAction);
929
registerAction2(OpenModePickerAction);
930
registerAction2(OpenSessionTargetPickerAction);
931
registerAction2(OpenDelegationPickerAction);
932
registerAction2(OpenWorkspacePickerAction);
933
registerAction2(ChatSessionPrimaryPickerAction);
934
registerAction2(ChangeChatModelAction);
935
registerAction2(CancelEdit);
936
}
937
938