Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts
5267 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Codicon } from '../../../../../base/common/codicons.js';6import { hash } from '../../../../../base/common/hash.js';7import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';8import { basename } from '../../../../../base/common/resources.js';9import { ThemeIcon } from '../../../../../base/common/themables.js';10import { assertType } from '../../../../../base/common/types.js';11import { URI } from '../../../../../base/common/uri.js';12import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';13import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';14import { localize, localize2 } from '../../../../../nls.js';15import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';16import { ICommandService } from '../../../../../platform/commands/common/commands.js';17import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';18import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';19import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';20import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';21import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';22import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';23import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';24import { IChatMode, IChatModeService } from '../../common/chatModes.js';25import { chatVariableLeader } from '../../common/requestParser/chatParserTypes.js';26import { IChatService } from '../../common/chatService/chatService.js';27import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js';28import { ILanguageModelChatMetadata } from '../../common/languageModels.js';29import { ILanguageModelToolsService } from '../../common/tools/languageModelToolsService.js';30import { PromptsStorage } from '../../common/promptSyntax/service/promptsService.js';31import { IChatSessionsService } from '../../common/chatSessionsService.js';32import { IChatWidget, IChatWidgetService } from '../chat.js';33import { getAgentSessionProvider, AgentSessionProviders } from '../agentSessions/agentSessions.js';34import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js';35import { ctxHasEditorModification, ctxHasRequestInProgress, ctxIsGlobalEditingSession } from '../chatEditing/chatEditingEditorContextKeys.js';36import { ACTION_ID_NEW_CHAT, CHAT_CATEGORY, handleCurrentEditingSession, handleModeSwitch } from './chatActions.js';37import { CreateRemoteAgentJobAction } from './chatContinueInAction.js';3839export interface IVoiceChatExecuteActionContext {40readonly disableTimeout?: boolean;41}4243export interface IChatExecuteActionContext {44widget?: IChatWidget;45inputValue?: string;46voice?: IVoiceChatExecuteActionContext;47}4849abstract class SubmitAction extends Action2 {50async run(accessor: ServicesAccessor, ...args: unknown[]) {51const context = args[0] as IChatExecuteActionContext | undefined;52const telemetryService = accessor.get(ITelemetryService);53const widgetService = accessor.get(IChatWidgetService);54const widget = context?.widget ?? widgetService.lastFocusedWidget;5556// Check if there's a pending delegation target57const pendingDelegationTarget = widget?.input.pendingDelegationTarget;58if (pendingDelegationTarget && pendingDelegationTarget !== AgentSessionProviders.Local) {59return await this.handleDelegation(accessor, widget, pendingDelegationTarget);60}6162if (widget?.viewModel?.editing) {63const configurationService = accessor.get(IConfigurationService);64const dialogService = accessor.get(IDialogService);65const chatService = accessor.get(IChatService);66const chatModel = chatService.getSession(widget.viewModel.sessionResource);67if (!chatModel) {68return;69}7071const session = chatModel.editingSession;72if (!session) {73return;74}7576const requestId = widget.viewModel?.editing.id;7778if (requestId) {79const chatRequests = chatModel.getRequests();80const itemIndex = chatRequests.findIndex(request => request.id === requestId);81const editsToUndo = chatRequests.length - itemIndex;8283const requestsToRemove = chatRequests.slice(itemIndex);84const requestIdsToRemove = new Set(requestsToRemove.map(request => request.id));85const entriesModifiedInRequestsToRemove = session.entries.get().filter((entry) => requestIdsToRemove.has(entry.lastModifyingRequestId)) ?? [];86const shouldPrompt = entriesModifiedInRequestsToRemove.length > 0 && configurationService.getValue('chat.editing.confirmEditRequestRemoval') === true;8788let message: string;89if (editsToUndo === 1) {90if (entriesModifiedInRequestsToRemove.length === 1) {91message = 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));92} else {93message = 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);94}95} else {96if (entriesModifiedInRequestsToRemove.length === 1) {97message = 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));98} else {99message = 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);100}101}102103const confirmation = shouldPrompt104? await dialogService.confirm({105title: editsToUndo === 1106? localize('chat.removeLast.confirmation.title', "Do you want to undo your last edit?")107: localize('chat.remove.confirmation.title', "Do you want to undo {0} edits?", editsToUndo),108message: message,109primaryButton: localize('chat.remove.confirmation.primaryButton', "Yes"),110checkbox: { label: localize('chat.remove.confirmation.checkbox', "Don't ask again"), checked: false },111type: 'info'112})113: { confirmed: true };114115type EditUndoEvent = {116editRequestType: string;117outcome: 'cancelled' | 'applied';118editsUndoCount: number;119};120121type EditUndoEventClassification = {122owner: 'justschen';123comment: 'Event used to gain insights into when there are pending changes to undo, and whether edited requests are applied or cancelled.';124editRequestType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Current entry point for editing a request.' };125outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the edit was cancelled or applied.' };126editsUndoCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of edits that would be undone.'; 'isMeasurement': true };127};128129if (!confirmation.confirmed) {130telemetryService.publicLog2<EditUndoEvent, EditUndoEventClassification>('chat.undoEditsConfirmation', {131editRequestType: configurationService.getValue<string>('chat.editRequests'),132outcome: 'cancelled',133editsUndoCount: editsToUndo134});135return;136} else if (editsToUndo > 0) {137telemetryService.publicLog2<EditUndoEvent, EditUndoEventClassification>('chat.undoEditsConfirmation', {138editRequestType: configurationService.getValue<string>('chat.editRequests'),139outcome: 'applied',140editsUndoCount: editsToUndo141});142}143144if (confirmation.checkboxChecked) {145await configurationService.updateValue('chat.editing.confirmEditRequestRemoval', false);146}147148// Restore the snapshot to what it was before the request(s) that we deleted149const snapshotRequestId = chatRequests[itemIndex].id;150await session.restoreSnapshot(snapshotRequestId, undefined);151}152} else if (widget?.viewModel?.model.checkpoint) {153widget.viewModel.model.setCheckpoint(undefined);154}155widget?.acceptInput(context?.inputValue);156}157158private async handleDelegation(accessor: ServicesAccessor, widget: IChatWidget, delegationTarget: Exclude<AgentSessionProviders, AgentSessionProviders.Local>): Promise<void> {159const chatSessionsService = accessor.get(IChatSessionsService);160161// Find the contribution for the delegation target162const contributions = chatSessionsService.getAllChatSessionContributions();163const targetContribution = contributions.find(contrib => {164const providerType = getAgentSessionProvider(contrib.type);165return providerType === delegationTarget;166});167168if (!targetContribution) {169throw new Error(`No contribution found for delegation target: ${delegationTarget}`);170}171172if (targetContribution.canDelegate === false) {173throw new Error(`The contribution for delegation target: ${delegationTarget} does not support delegation.`);174}175176return new CreateRemoteAgentJobAction().run(accessor, targetContribution, widget);177}178}179180const requestInProgressOrPendingToolCall = ContextKeyExpr.or(ChatContextKeys.requestInProgress, ChatContextKeys.Editing.hasToolConfirmation);181const whenNotInProgress = ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), ChatContextKeys.Editing.hasToolConfirmation.negate());182183export class ChatSubmitAction extends SubmitAction {184static readonly ID = 'workbench.action.chat.submit';185186constructor() {187const menuCondition = ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask);188const precondition = ContextKeyExpr.and(189ChatContextKeys.inputHasText,190whenNotInProgress,191ChatContextKeys.chatSessionOptionsValid,192);193194super({195id: ChatSubmitAction.ID,196title: localize2('interactive.submit.label', "Send"),197f1: false,198category: CHAT_CATEGORY,199icon: Codicon.send,200precondition,201toggled: {202condition: ChatContextKeys.lockedToCodingAgent,203icon: Codicon.send,204tooltip: localize('sendToAgent', "Send to Agent"),205},206keybinding: {207when: ContextKeyExpr.and(208ChatContextKeys.inChatInput,209ChatContextKeys.withinEditSessionDiff.negate(),210),211primary: KeyCode.Enter,212weight: KeybindingWeight.EditorContrib213},214menu: [215{216id: MenuId.ChatExecute,217order: 4,218when: ContextKeyExpr.and(219whenNotInProgress,220menuCondition,221ChatContextKeys.withinEditSessionDiff.negate(),222),223group: 'navigation',224alt: {225id: 'workbench.action.chat.sendToNewChat',226title: localize2('chat.newChat.label', "Send to New Chat"),227icon: Codicon.plus228}229}, {230id: MenuId.ChatEditorInlineExecute,231group: 'navigation',232order: 4,233when: ContextKeyExpr.and(234ContextKeyExpr.or(ctxHasEditorModification.negate(), ChatContextKeys.inputHasText),235whenNotInProgress,236ChatContextKeys.requestInProgress.negate(),237menuCondition238),239}]240});241}242}243244245export const ToggleAgentModeActionId = 'workbench.action.chat.toggleAgentMode';246247export interface IToggleChatModeArgs {248modeId: ChatModeKind | string;249sessionResource: URI | undefined;250}251252type ChatModeChangeClassification = {253owner: 'digitarald';254comment: 'Reporting when agent is switched between different modes';255fromMode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The previous agent name' };256mode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The new agent name' };257requestCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of requests in the current chat session'; 'isMeasurement': true };258storage?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Source of the target mode (builtin, local, user, extension)' };259extensionId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension ID if the target mode is from an extension' };260toolsCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of custom tools in the target mode'; 'isMeasurement': true };261handoffsCount?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of handoffs in the target mode'; 'isMeasurement': true };262};263264type ChatModeChangeEvent = {265fromMode: string;266mode: string;267requestCount: number;268storage?: string;269extensionId?: string;270toolsCount?: number;271handoffsCount?: number;272};273274class ToggleChatModeAction extends Action2 {275276static readonly ID = ToggleAgentModeActionId;277278constructor() {279super({280id: ToggleChatModeAction.ID,281title: localize2('interactive.toggleAgent.label', "Switch to Next Agent"),282f1: true,283category: CHAT_CATEGORY,284precondition: ContextKeyExpr.and(285ChatContextKeys.enabled,286ChatContextKeys.requestInProgress.negate())287});288}289290async run(accessor: ServicesAccessor, ...args: unknown[]) {291const commandService = accessor.get(ICommandService);292const configurationService = accessor.get(IConfigurationService);293const instaService = accessor.get(IInstantiationService);294const modeService = accessor.get(IChatModeService);295const telemetryService = accessor.get(ITelemetryService);296const chatWidgetService = accessor.get(IChatWidgetService);297298const arg = args.at(0) as IToggleChatModeArgs | undefined;299let widget: IChatWidget | undefined;300if (arg?.sessionResource) {301widget = chatWidgetService.getWidgetBySessionResource(arg.sessionResource);302} else {303widget = getEditingSessionContext(accessor, args)?.chatWidget;304}305306if (!widget) {307return;308}309310const chatSession = widget.viewModel?.model;311const requestCount = chatSession?.getRequests().length ?? 0;312const switchToMode = (arg && (modeService.findModeById(arg.modeId) || modeService.findModeByName(arg.modeId))) ?? this.getNextMode(widget, requestCount, configurationService, modeService);313314const currentMode = widget.input.currentModeObs.get();315if (switchToMode.id === currentMode.id) {316return;317}318319const chatModeCheck = await instaService.invokeFunction(handleModeSwitch, widget.input.currentModeKind, switchToMode.kind, requestCount, widget.viewModel?.model);320if (!chatModeCheck) {321return;322}323324// Send telemetry for mode change325const storage = switchToMode.source?.storage ?? 'builtin';326const extensionId = switchToMode.source?.storage === 'extension' ? switchToMode.source.extensionId.value : undefined;327const toolsCount = switchToMode.customTools?.get()?.length ?? 0;328const handoffsCount = switchToMode.handOffs?.get()?.length ?? 0;329330// Hash names for user/workspace modes to only instrument non-user agent names331const getModeNameForTelemetry = (mode: IChatMode): string => {332const modeStorage = mode.source?.storage;333if (modeStorage === PromptsStorage.local || modeStorage === PromptsStorage.user) {334return String(hash(mode.name.get()));335}336return mode.name.get();337};338339telemetryService.publicLog2<ChatModeChangeEvent, ChatModeChangeClassification>('chat.modeChange', {340fromMode: getModeNameForTelemetry(currentMode),341mode: getModeNameForTelemetry(switchToMode),342requestCount: requestCount,343storage,344extensionId,345toolsCount,346handoffsCount347});348349widget.input.setChatMode(switchToMode.id);350351if (chatModeCheck.needToClearSession) {352await commandService.executeCommand(ACTION_ID_NEW_CHAT);353}354}355356private getNextMode(chatWidget: IChatWidget, requestCount: number, configurationService: IConfigurationService, modeService: IChatModeService): IChatMode {357const modes = modeService.getModes();358const flat = [359...modes.builtin.filter(mode => {360return mode.kind !== ChatModeKind.Edit || configurationService.getValue(ChatConfiguration.Edits2Enabled) || requestCount === 0;361}),362...(modes.custom ?? []),363];364365const curModeIndex = flat.findIndex(mode => mode.id === chatWidget.input.currentModeObs.get().id);366const newMode = flat[(curModeIndex + 1) % flat.length];367return newMode;368}369}370371class SwitchToNextModelAction extends Action2 {372static readonly ID = 'workbench.action.chat.switchToNextModel';373374constructor() {375super({376id: SwitchToNextModelAction.ID,377title: localize2('interactive.switchToNextModel.label', "Switch to Next Model"),378category: CHAT_CATEGORY,379f1: true,380precondition: ChatContextKeys.enabled,381});382}383384override run(accessor: ServicesAccessor, ...args: unknown[]): void {385const widgetService = accessor.get(IChatWidgetService);386const widget = widgetService.lastFocusedWidget;387widget?.input.switchToNextModel();388}389}390391export class OpenModelPickerAction extends Action2 {392static readonly ID = 'workbench.action.chat.openModelPicker';393394constructor() {395super({396id: OpenModelPickerAction.ID,397title: localize2('interactive.openModelPicker.label', "Open Model Picker"),398category: CHAT_CATEGORY,399f1: false,400keybinding: {401primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Period,402weight: KeybindingWeight.WorkbenchContrib,403when: ChatContextKeys.inChatInput404},405precondition: ChatContextKeys.enabled,406menu: {407id: MenuId.ChatInput,408order: 3,409group: 'navigation',410when:411ContextKeyExpr.and(412ChatContextKeys.lockedToCodingAgent.negate(),413ContextKeyExpr.or(414ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Chat),415ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.EditorInline),416ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Notebook),417ContextKeyExpr.equals(ChatContextKeys.location.key, ChatAgentLocation.Terminal)),418// Hide in welcome view when session type is not local419ContextKeyExpr.or(420ChatContextKeys.inAgentSessionsWelcome.negate(),421ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local))422)423}424});425}426427override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {428const widgetService = accessor.get(IChatWidgetService);429const widget = widgetService.lastFocusedWidget;430if (widget) {431await widgetService.reveal(widget);432widget.input.openModelPicker();433}434}435}436export class OpenModePickerAction extends Action2 {437static readonly ID = 'workbench.action.chat.openModePicker';438439constructor() {440super({441id: OpenModePickerAction.ID,442title: localize2('interactive.openModePicker.label', "Open Agent Picker"),443tooltip: localize('setChatMode', "Set Agent"),444category: CHAT_CATEGORY,445f1: false,446precondition: ChatContextKeys.enabled,447keybinding: {448when: ContextKeyExpr.and(449ChatContextKeys.inChatInput,450ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)),451primary: KeyMod.CtrlCmd | KeyCode.Period,452weight: KeybindingWeight.EditorContrib453},454menu: [455{456id: MenuId.ChatInput,457order: 1,458when: ContextKeyExpr.and(459ChatContextKeys.enabled,460ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),461ChatContextKeys.inQuickChat.negate(),462ContextKeyExpr.or(463ChatContextKeys.lockedToCodingAgent.negate(),464ChatContextKeys.chatSessionHasCustomAgentTarget),465// Hide in welcome view when session type is not local466ContextKeyExpr.or(467ChatContextKeys.inAgentSessionsWelcome.negate(),468ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local))),469group: 'navigation',470},471]472});473}474475override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {476const widgetService = accessor.get(IChatWidgetService);477const widget = widgetService.lastFocusedWidget;478if (widget) {479widget.input.openModePicker();480}481}482}483484export class OpenSessionTargetPickerAction extends Action2 {485static readonly ID = 'workbench.action.chat.openSessionTargetPicker';486487constructor() {488super({489id: OpenSessionTargetPickerAction.ID,490title: localize2('interactive.openSessionTargetPicker.label', "Open Session Target Picker"),491tooltip: localize('setSessionTarget', "Set Session Target"),492category: CHAT_CATEGORY,493f1: false,494precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.or(ChatContextKeys.chatSessionIsEmpty, ChatContextKeys.inAgentSessionsWelcome), ChatContextKeys.currentlyEditingInput.negate(), ChatContextKeys.currentlyEditing.negate()),495menu: [496{497id: MenuId.ChatInput,498order: 0,499when: ContextKeyExpr.and(500ChatContextKeys.enabled,501ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),502ChatContextKeys.inQuickChat.negate(),503ChatContextKeys.chatSessionIsEmpty),504group: 'navigation',505},506]507});508}509510override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {511const widgetService = accessor.get(IChatWidgetService);512const widget = widgetService.lastFocusedWidget;513if (widget) {514widget.input.openSessionTargetPicker();515}516}517}518519export class OpenDelegationPickerAction extends Action2 {520static readonly ID = 'workbench.action.chat.openDelegationPicker';521522constructor() {523super({524id: OpenDelegationPickerAction.ID,525title: localize2('interactive.openDelegationPicker.label', "Open Delegation Picker"),526tooltip: localize('delegateSession', "Delegate Session"),527category: CHAT_CATEGORY,528f1: false,529precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.chatSessionIsEmpty.negate(), ChatContextKeys.currentlyEditingInput.negate(), ChatContextKeys.currentlyEditing.negate()),530menu: [531{532id: MenuId.ChatInput,533order: 0.5,534when: ContextKeyExpr.and(535ChatContextKeys.enabled,536ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),537ChatContextKeys.inQuickChat.negate(),538ChatContextKeys.chatSessionIsEmpty.negate()),539group: 'navigation',540},541]542});543}544545override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {546const widgetService = accessor.get(IChatWidgetService);547const widget = widgetService.lastFocusedWidget;548if (widget) {549widget.input.openDelegationPicker();550}551}552}553554export class OpenWorkspacePickerAction extends Action2 {555static readonly ID = 'workbench.action.chat.openWorkspacePicker';556557constructor() {558super({559id: OpenWorkspacePickerAction.ID,560title: localize2('interactive.openWorkspacePicker.label', "Open Workspace Picker"),561tooltip: localize('selectWorkspace', "Select Target Workspace"),562category: CHAT_CATEGORY,563f1: false,564precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.inAgentSessionsWelcome),565menu: [566{567id: MenuId.ChatInput,568order: 0.6,569when: ContextKeyExpr.and(570ChatContextKeys.inAgentSessionsWelcome,571ChatContextKeys.chatSessionType.isEqualTo('local')572),573group: 'navigation',574},575]576});577}578579override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {580// The picker is opened via the action view item581}582}583584export class ChatSessionPrimaryPickerAction extends Action2 {585static readonly ID = 'workbench.action.chat.chatSessionPrimaryPicker';586constructor() {587super({588id: ChatSessionPrimaryPickerAction.ID,589title: localize2('interactive.openChatSessionPrimaryPicker.label', "Open Model Picker"),590category: CHAT_CATEGORY,591f1: false,592precondition: ChatContextKeys.enabled,593menu: {594id: MenuId.ChatInput,595order: 4,596group: 'navigation',597when:598ContextKeyExpr.and(599ChatContextKeys.chatSessionHasModels,600ContextKeyExpr.or(601ChatContextKeys.lockedToCodingAgent,602ContextKeyExpr.and(603ChatContextKeys.inAgentSessionsWelcome,604ChatContextKeys.chatSessionType.notEqualsTo('local')605)606)607)608}609});610}611612override async run(accessor: ServicesAccessor, ...args: unknown[]): Promise<void> {613const widgetService = accessor.get(IChatWidgetService);614const widget = widgetService.lastFocusedWidget;615if (widget) {616widget.input.openChatSessionPicker();617}618}619}620621export const ChangeChatModelActionId = 'workbench.action.chat.changeModel';622class ChangeChatModelAction extends Action2 {623static readonly ID = ChangeChatModelActionId;624625constructor() {626super({627id: ChangeChatModelAction.ID,628title: localize2('interactive.changeModel.label', "Change Model"),629category: CHAT_CATEGORY,630f1: false,631precondition: ChatContextKeys.enabled,632});633}634635override run(accessor: ServicesAccessor, ...args: unknown[]): void {636const modelInfo = args[0] as Pick<ILanguageModelChatMetadata, 'vendor' | 'id' | 'family'>;637// Type check the arg638assertType(typeof modelInfo.vendor === 'string' && typeof modelInfo.id === 'string' && typeof modelInfo.family === 'string');639const widgetService = accessor.get(IChatWidgetService);640const widgets = widgetService.getAllWidgets();641for (const widget of widgets) {642widget.input.switchModel(modelInfo);643}644}645}646647export class ChatEditingSessionSubmitAction extends SubmitAction {648static readonly ID = 'workbench.action.edits.submit';649650constructor() {651const notInProgressOrEditing = ContextKeyExpr.and(652ContextKeyExpr.or(whenNotInProgress, ChatContextKeys.editingRequestType.isEqualTo(ChatContextKeys.EditingRequestType.Sent)),653ChatContextKeys.editingRequestType.notEqualsTo(ChatContextKeys.EditingRequestType.QueueOrSteer)654);655656const menuCondition = ChatContextKeys.chatModeKind.notEqualsTo(ChatModeKind.Ask);657const precondition = ContextKeyExpr.and(658ChatContextKeys.inputHasText,659notInProgressOrEditing,660ChatContextKeys.chatSessionOptionsValid661);662663super({664id: ChatEditingSessionSubmitAction.ID,665title: localize2('edits.submit.label', "Send"),666f1: false,667category: CHAT_CATEGORY,668icon: Codicon.send,669precondition,670menu: [671{672id: MenuId.ChatExecute,673order: 4,674when: ContextKeyExpr.and(675notInProgressOrEditing,676menuCondition),677group: 'navigation',678alt: {679id: 'workbench.action.chat.sendToNewChat',680title: localize2('chat.newChat.label', "Send to New Chat"),681icon: Codicon.plus682}683}]684});685}686}687688class SubmitWithoutDispatchingAction extends Action2 {689static readonly ID = 'workbench.action.chat.submitWithoutDispatching';690691constructor() {692const precondition = ContextKeyExpr.and(693ChatContextKeys.inputHasText,694whenNotInProgress,695ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask),696);697698super({699id: SubmitWithoutDispatchingAction.ID,700title: localize2('interactive.submitWithoutDispatch.label', "Send"),701f1: false,702category: CHAT_CATEGORY,703precondition,704keybinding: {705when: ChatContextKeys.inChatInput,706primary: KeyMod.Alt | KeyMod.Shift | KeyCode.Enter,707weight: KeybindingWeight.EditorContrib708}709});710}711712run(accessor: ServicesAccessor, ...args: unknown[]) {713const context = args[0] as IChatExecuteActionContext | undefined;714715const widgetService = accessor.get(IChatWidgetService);716const widget = context?.widget ?? widgetService.lastFocusedWidget;717widget?.acceptInput(context?.inputValue, { noCommandDetection: true });718}719}720721export class ChatSubmitWithCodebaseAction extends Action2 {722static readonly ID = 'workbench.action.chat.submitWithCodebase';723724constructor() {725const precondition = ContextKeyExpr.and(726ChatContextKeys.inputHasText,727whenNotInProgress,728);729730super({731id: ChatSubmitWithCodebaseAction.ID,732title: localize2('actions.chat.submitWithCodebase', "Send with {0}", `${chatVariableLeader}codebase`),733precondition,734keybinding: {735when: ChatContextKeys.inChatInput,736primary: KeyMod.CtrlCmd | KeyCode.Enter,737weight: KeybindingWeight.EditorContrib738},739});740}741742run(accessor: ServicesAccessor, ...args: unknown[]) {743const context = args[0] as IChatExecuteActionContext | undefined;744745const widgetService = accessor.get(IChatWidgetService);746const widget = context?.widget ?? widgetService.lastFocusedWidget;747if (!widget) {748return;749}750751const languageModelToolsService = accessor.get(ILanguageModelToolsService);752const codebaseTool = languageModelToolsService.getToolByName('codebase');753if (!codebaseTool) {754return;755}756757widget.input.attachmentModel.addContext({758id: codebaseTool.id,759name: codebaseTool.displayName ?? '',760fullName: codebaseTool.displayName ?? '',761value: undefined,762icon: ThemeIcon.isThemeIcon(codebaseTool.icon) ? codebaseTool.icon : undefined,763kind: 'tool'764});765widget.acceptInput();766}767}768769class SendToNewChatAction extends Action2 {770constructor() {771const precondition = ChatContextKeys.inputHasText;772773super({774id: 'workbench.action.chat.sendToNewChat',775title: localize2('chat.newChat.label', "Send to New Chat"),776precondition,777category: CHAT_CATEGORY,778f1: false,779keybinding: {780weight: KeybindingWeight.WorkbenchContrib,781primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,782when: ChatContextKeys.inChatInput,783}784});785}786787async run(accessor: ServicesAccessor, ...args: unknown[]) {788const context = args[0] as IChatExecuteActionContext | undefined;789790const widgetService = accessor.get(IChatWidgetService);791const dialogService = accessor.get(IDialogService);792const chatService = accessor.get(IChatService);793const widget = context?.widget ?? widgetService.lastFocusedWidget;794if (!widget) {795return;796}797798const inputBeforeClear = widget.getInput();799800// Cancel any in-progress request before clearing801if (widget.viewModel) {802chatService.cancelCurrentRequestForSession(widget.viewModel.sessionResource);803}804805if (widget.viewModel?.model) {806if (!(await handleCurrentEditingSession(widget.viewModel.model, undefined, dialogService))) {807return;808}809}810811// Clear the input from the current session before creating a new one812widget.setInput('');813814await widget.clear();815widget.acceptInput(inputBeforeClear, { storeToHistory: true });816}817}818819export const CancelChatActionId = 'workbench.action.chat.cancel';820export class CancelAction extends Action2 {821static readonly ID = CancelChatActionId;822constructor() {823super({824id: CancelAction.ID,825title: localize2('interactive.cancel.label', "Cancel"),826f1: false,827category: CHAT_CATEGORY,828icon: Codicon.stopCircle,829menu: [{830id: MenuId.ChatExecute,831when: ContextKeyExpr.and(832requestInProgressOrPendingToolCall,833ChatContextKeys.remoteJobCreating.negate(),834ChatContextKeys.currentlyEditing.negate(),835),836order: 4,837group: 'navigation',838}, {839id: MenuId.ChatEditorInlineExecute,840when: ContextKeyExpr.and(841ctxIsGlobalEditingSession.negate(),842ctxHasRequestInProgress,843),844order: 4,845group: 'navigation',846}847],848keybinding: {849weight: KeybindingWeight.WorkbenchContrib,850primary: KeyMod.CtrlCmd | KeyCode.Escape,851when: ContextKeyExpr.and(852requestInProgressOrPendingToolCall,853ChatContextKeys.remoteJobCreating.negate()854),855win: { primary: KeyMod.Alt | KeyCode.Backspace },856}857});858}859860run(accessor: ServicesAccessor, ...args: unknown[]) {861const context = args[0] as IChatExecuteActionContext | undefined;862const widgetService = accessor.get(IChatWidgetService);863const widget = context?.widget ?? widgetService.lastFocusedWidget;864if (!widget) {865return;866}867868const chatService = accessor.get(IChatService);869if (widget.viewModel) {870chatService.cancelCurrentRequestForSession(widget.viewModel.sessionResource);871}872}873}874875export const CancelChatEditId = 'workbench.edit.chat.cancel';876export class CancelEdit extends Action2 {877static readonly ID = CancelChatEditId;878constructor() {879super({880id: CancelEdit.ID,881title: localize2('interactive.cancelEdit.label', "Cancel Edit"),882f1: false,883category: CHAT_CATEGORY,884icon: Codicon.x,885menu: [886{887id: MenuId.ChatMessageTitle,888group: 'navigation',889order: 1,890when: ContextKeyExpr.and(ChatContextKeys.isRequest, ChatContextKeys.currentlyEditing, ContextKeyExpr.equals(`config.${ChatConfiguration.EditRequests}`, 'input'))891}892],893keybinding: {894primary: KeyCode.Escape,895when: ContextKeyExpr.and(ChatContextKeys.inChatInput,896EditorContextKeys.hoverVisible.toNegated(),897EditorContextKeys.hasNonEmptySelection.toNegated(),898EditorContextKeys.hasMultipleSelections.toNegated(),899ContextKeyExpr.or(ChatContextKeys.currentlyEditing, ChatContextKeys.currentlyEditingInput)),900weight: KeybindingWeight.EditorContrib - 5901}902});903}904905run(accessor: ServicesAccessor, ...args: unknown[]) {906const context = args[0] as IChatExecuteActionContext | undefined;907908const widgetService = accessor.get(IChatWidgetService);909const widget = context?.widget ?? widgetService.lastFocusedWidget;910if (!widget) {911return;912}913widget.finishedEditing();914}915}916917918export function registerChatExecuteActions() {919registerAction2(ChatSubmitAction);920registerAction2(ChatEditingSessionSubmitAction);921registerAction2(SubmitWithoutDispatchingAction);922registerAction2(CancelAction);923registerAction2(SendToNewChatAction);924registerAction2(ChatSubmitWithCodebaseAction);925registerAction2(ToggleChatModeAction);926registerAction2(SwitchToNextModelAction);927registerAction2(OpenModelPickerAction);928registerAction2(OpenModePickerAction);929registerAction2(OpenSessionTargetPickerAction);930registerAction2(OpenDelegationPickerAction);931registerAction2(OpenWorkspacePickerAction);932registerAction2(ChatSessionPrimaryPickerAction);933registerAction2(ChangeChatModelAction);934registerAction2(CancelEdit);935}936937938