Path: blob/main/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts
5310 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 { localize, localize2 } from '../../../../../nls.js';6import { AgentSessionSection, IAgentSession, IAgentSessionSection, IMarshalledAgentSessionContext, isAgentSessionSection, isLocalAgentSessionItem, isMarshalledAgentSessionContext } from './agentSessionsModel.js';7import { Action2, MenuId, MenuRegistry } from '../../../../../platform/actions/common/actions.js';8import { Codicon } from '../../../../../base/common/codicons.js';9import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';10import { AGENT_SESSION_DELETE_ACTION_ID, AGENT_SESSION_RENAME_ACTION_ID, AgentSessionProviders, AgentSessionsViewerOrientation, IAgentSessionsControl } from './agentSessions.js';11import { IChatService } from '../../common/chatService/chatService.js';12import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';13import { IChatEditorOptions } from '../widgetHosts/editor/chatEditor.js';14import { ChatViewId, IChatWidgetService } from '../chat.js';15import { ACTIVE_GROUP, AUX_WINDOW_GROUP, PreferredGroup, SIDE_GROUP } from '../../../../services/editor/common/editorService.js';16import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js';17import { IWorkbenchLayoutService, Position } from '../../../../services/layout/browser/layoutService.js';18import { IAgentSessionsService } from './agentSessionsService.js';19import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';20import { ChatEditorInput, showClearEditingSessionConfirmation } from '../widgetHosts/editor/chatEditorInput.js';21import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';22import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';23import { ChatConfiguration } from '../../common/constants.js';24import { ACTION_ID_NEW_CHAT } from '../actions/chatActions.js';25import { IViewsService } from '../../../../services/views/common/viewsService.js';26import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';27import { ICommandService } from '../../../../../platform/commands/common/commands.js';28import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';29import { AgentSessionsPicker } from './agentSessionsPicker.js';30import { ActiveEditorContext } from '../../../../common/contextkeys.js';31import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js';32import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';33import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';34import { coalesce } from '../../../../../base/common/arrays.js';35import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';36import { IPaneCompositePartService } from '../../../../services/panecomposite/browser/panecomposite.js';3738const AGENT_SESSIONS_CATEGORY = localize2('chatSessions', "Chat Agent Sessions");3940//#region Chat View4142export class ToggleShowAgentSessionsAction extends Action2 {4344constructor() {45super({46id: 'workbench.action.chat.toggleShowAgentSessions',47title: localize2('chat.showSessions', "Show Sessions"),48toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),49menu: {50id: MenuId.ChatWelcomeContext,51group: '0_sessions',52order: 2,53when: ChatContextKeys.inChatEditor.negate()54}55});56}5758async run(accessor: ServicesAccessor): Promise<void> {59const configurationService = accessor.get(IConfigurationService);60const currentValue = configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsEnabled);61await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, !currentValue);62}63}6465const agentSessionsOrientationSubmenu = new MenuId('chatAgentSessionsOrientationSubmenu');66MenuRegistry.appendMenuItem(MenuId.ChatWelcomeContext, {67submenu: agentSessionsOrientationSubmenu,68title: localize2('chat.sessionsOrientation', "Sessions Orientation"),69group: '0_sessions',70order: 1,71when: ChatContextKeys.inChatEditor.negate()72});7374export class SetAgentSessionsOrientationStackedAction extends Action2 {7576constructor() {77super({78id: 'workbench.action.chat.setAgentSessionsOrientationStacked',79title: localize2('chat.sessionsOrientation.stacked', "Stacked"),80toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsOrientation}`, 'stacked'),81precondition: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),82menu: {83id: agentSessionsOrientationSubmenu,84group: 'navigation',85order: 286}87});88}8990async run(accessor: ServicesAccessor): Promise<void> {91const commandService = accessor.get(ICommandService);9293await commandService.executeCommand(HideAgentSessionsSidebar.ID);94}95}9697export class SetAgentSessionsOrientationSideBySideAction extends Action2 {9899constructor() {100super({101id: 'workbench.action.chat.setAgentSessionsOrientationSideBySide',102title: localize2('chat.sessionsOrientation.sideBySide', "Side by Side"),103toggled: ContextKeyExpr.notEquals(`config.${ChatConfiguration.ChatViewSessionsOrientation}`, 'stacked'),104precondition: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),105menu: {106id: agentSessionsOrientationSubmenu,107group: 'navigation',108order: 1109}110});111}112113async run(accessor: ServicesAccessor): Promise<void> {114const commandService = accessor.get(ICommandService);115116await commandService.executeCommand(ShowAgentSessionsSidebar.ID);117}118}119120export class PickAgentSessionAction extends Action2 {121122constructor() {123super({124id: `workbench.action.chat.history`,125title: localize2('agentSessions.open', "Open Agent Session..."),126menu: [127{128id: MenuId.ViewTitle,129when: ContextKeyExpr.and(130ContextKeyExpr.equals('view', ChatViewId),131ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, false)132),133group: 'navigation',134order: 2135},136{137id: MenuId.EditorTitle,138when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID),139}140],141category: AGENT_SESSIONS_CATEGORY,142icon: Codicon.history,143f1: true,144precondition: ChatContextKeys.enabled145});146}147148async run(accessor: ServicesAccessor): Promise<void> {149const instantiationService = accessor.get(IInstantiationService);150151const agentSessionsPicker = instantiationService.createInstance(AgentSessionsPicker, undefined);152await agentSessionsPicker.pickAgentSession();153}154}155156export class ArchiveAllAgentSessionsAction extends Action2 {157158constructor() {159super({160id: 'workbench.action.chat.archiveAllAgentSessions',161title: localize2('archiveAll.label', "Archive All Workspace Agent Sessions"),162precondition: ChatContextKeys.enabled,163category: AGENT_SESSIONS_CATEGORY,164f1: true,165});166}167async run(accessor: ServicesAccessor) {168const agentSessionsService = accessor.get(IAgentSessionsService);169const dialogService = accessor.get(IDialogService);170171const sessionsToArchive = agentSessionsService.model.sessions.filter(session => !session.isArchived());172if (sessionsToArchive.length === 0) {173return;174}175176const confirmed = await dialogService.confirm({177message: sessionsToArchive.length === 1178? localize('archiveAllSessions.confirmSingle', "Are you sure you want to archive 1 agent session?")179: localize('archiveAllSessions.confirm', "Are you sure you want to archive {0} agent sessions?", sessionsToArchive.length),180detail: localize('archiveAllSessions.detail', "You can unarchive sessions later if needed from the Chat view."),181primaryButton: localize('archiveAllSessions.archive', "Archive")182});183184if (!confirmed.confirmed) {185return;186}187188for (const session of sessionsToArchive) {189session.setArchived(true);190}191}192}193194export class MarkAllAgentSessionsReadAction extends Action2 {195196constructor() {197super({198id: 'workbench.action.chat.markAllAgentSessionsRead',199title: localize2('markAllRead.label', "Mark All as Read"),200precondition: ChatContextKeys.enabled,201category: AGENT_SESSIONS_CATEGORY,202f1: true,203menu: {204id: MenuId.AgentSessionsContext,205group: '0_read',206order: 2,207when: ChatContextKeys.isArchivedAgentSession.negate() // no read state for archived sessions208}209});210}211async run(accessor: ServicesAccessor) {212const agentSessionsService = accessor.get(IAgentSessionsService);213214const sessionsToMarkRead = agentSessionsService.model.sessions.filter(session => !session.isArchived() && !session.isRead());215if (sessionsToMarkRead.length === 0) {216return;217}218219for (const session of sessionsToMarkRead) {220session.setRead(true);221}222}223}224225const ConfirmArchiveStorageKey = 'chat.sessions.confirmArchive';226227export class ArchiveAgentSessionSectionAction extends Action2 {228229constructor() {230super({231id: 'agentSessionSection.archive',232title: localize2('archiveSection', "Archive All"),233icon: Codicon.archive,234menu: [{235id: MenuId.AgentSessionSectionToolbar,236group: 'navigation',237order: 1,238when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),239}, {240id: MenuId.AgentSessionSectionContext,241group: '1_edit',242order: 2,243when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),244}]245});246}247248async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {249if (!context || !isAgentSessionSection(context)) {250return;251}252253const dialogService = accessor.get(IDialogService);254const storageService = accessor.get(IStorageService);255256const skipConfirmation = storageService.getBoolean(ConfirmArchiveStorageKey, StorageScope.PROFILE, false);257if (!skipConfirmation) {258const confirmed = await dialogService.confirm({259message: context.sessions.length === 1260? localize('archiveSectionSessions.confirmSingle', "Are you sure you want to archive 1 agent session from '{0}'?", context.label)261: localize('archiveSectionSessions.confirm', "Are you sure you want to archive {0} agent sessions from '{1}'?", context.sessions.length, context.label),262detail: localize('archiveSectionSessions.detail', "You can unarchive sessions later if needed from the sessions view."),263primaryButton: localize('archiveSectionSessions.archive', "Archive All"),264checkbox: {265label: localize('doNotAskAgain', "Do not ask me again")266}267});268269if (!confirmed.confirmed) {270return;271}272273if (confirmed.checkboxChecked) {274storageService.store(ConfirmArchiveStorageKey, true, StorageScope.PROFILE, StorageTarget.USER);275}276}277278for (const session of context.sessions) {279session.setArchived(true);280}281}282}283284export class UnarchiveAgentSessionSectionAction extends Action2 {285286constructor() {287super({288id: 'agentSessionSection.unarchive',289title: localize2('unarchiveSection', "Unarchive All"),290icon: Codicon.unarchive,291menu: [{292id: MenuId.AgentSessionSectionToolbar,293group: 'navigation',294order: 1,295when: ChatContextKeys.agentSessionSection.isEqualTo(AgentSessionSection.Archived),296}, {297id: MenuId.AgentSessionSectionContext,298group: '1_edit',299order: 2,300when: ChatContextKeys.agentSessionSection.isEqualTo(AgentSessionSection.Archived),301}]302});303}304305async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {306if (!context || !isAgentSessionSection(context)) {307return;308}309310const dialogService = accessor.get(IDialogService);311const storageService = accessor.get(IStorageService);312313const skipConfirmation = storageService.getBoolean(ConfirmArchiveStorageKey, StorageScope.PROFILE, false);314if (!skipConfirmation) {315const confirmed = await dialogService.confirm({316message: context.sessions.length === 1317? localize('unarchiveSectionSessions.confirmSingle', "Are you sure you want to unarchive 1 agent session?")318: localize('unarchiveSectionSessions.confirm', "Are you sure you want to unarchive {0} agent sessions?", context.sessions.length),319primaryButton: localize('unarchiveSectionSessions.unarchive', "Unarchive All"),320checkbox: {321label: localize('doNotAskAgain', "Do not ask me again")322}323});324325if (!confirmed.confirmed) {326return;327}328329if (confirmed.checkboxChecked) {330storageService.store(ConfirmArchiveStorageKey, true, StorageScope.PROFILE, StorageTarget.USER);331}332}333334for (const session of context.sessions) {335session.setArchived(false);336}337}338}339340export class MarkAgentSessionSectionReadAction extends Action2 {341342constructor() {343super({344id: 'agentSessionSection.markRead',345title: localize2('markSectionRead', "Mark All as Read"),346menu: [{347id: MenuId.AgentSessionSectionContext,348group: '1_edit',349order: 1,350when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),351}]352});353}354355async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {356if (!context || !isAgentSessionSection(context)) {357return;358}359360for (const session of context.sessions) {361session.setRead(true);362}363}364}365366//#endregion367368//#region Session Actions369370abstract class BaseAgentSessionAction extends Action2 {371372async run(accessor: ServicesAccessor, context?: IAgentSession | IMarshalledAgentSessionContext): Promise<void> {373const agentSessionsService = accessor.get(IAgentSessionsService);374const viewsService = accessor.get(IViewsService);375376let sessions: IAgentSession[] = [];377if (isMarshalledAgentSessionContext(context)) {378sessions = coalesce((context.sessions ?? [context.session]).map(session => agentSessionsService.getSession(session.resource)));379} else if (context) {380sessions = [context];381}382383if (sessions.length === 0) {384const chatView = viewsService.getActiveViewWithId<ChatViewPane>(ChatViewId);385const focused = chatView?.getFocusedSessions().at(0);386if (focused) {387sessions = [focused];388}389}390391if (sessions.length > 0) {392await this.runWithSessions(sessions, accessor);393}394}395396abstract runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> | void;397}398399export class MarkAgentSessionUnreadAction extends BaseAgentSessionAction {400401constructor() {402super({403id: 'agentSession.markUnread',404title: localize2('markUnread', "Mark as Unread"),405menu: {406id: MenuId.AgentSessionsContext,407group: '0_read',408order: 1,409when: ContextKeyExpr.and(410ChatContextKeys.isReadAgentSession,411ChatContextKeys.isArchivedAgentSession.negate() // no read state for archived sessions412),413}414});415}416417runWithSessions(sessions: IAgentSession[]): void {418for (const session of sessions) {419session.setRead(false);420}421}422}423424export class MarkAgentSessionReadAction extends BaseAgentSessionAction {425426constructor() {427super({428id: 'agentSession.markRead',429title: localize2('markRead', "Mark as Read"),430menu: {431id: MenuId.AgentSessionsContext,432group: '0_read',433order: 1,434when: ContextKeyExpr.and(435ChatContextKeys.isReadAgentSession.negate(),436ChatContextKeys.isArchivedAgentSession.negate() // no read state for archived sessions437),438}439});440}441442runWithSessions(sessions: IAgentSession[]): void {443for (const session of sessions) {444session.setRead(true);445}446}447}448449export class ArchiveAgentSessionAction extends BaseAgentSessionAction {450451constructor() {452super({453id: 'agentSession.archive',454title: localize2('archive', "Archive"),455icon: Codicon.archive,456keybinding: {457primary: KeyCode.Delete,458mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },459weight: KeybindingWeight.WorkbenchContrib + 1,460when: ContextKeyExpr.and(461ChatContextKeys.agentSessionsViewerFocused,462ChatContextKeys.isArchivedAgentSession.negate()463)464},465menu: [{466id: MenuId.AgentSessionItemToolbar,467group: 'navigation',468order: 1,469when: ChatContextKeys.isArchivedAgentSession.negate(),470}, {471id: MenuId.AgentSessionsContext,472group: '1_edit',473order: 2,474when: ChatContextKeys.isArchivedAgentSession.negate()475}]476});477}478479async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {480const chatService = accessor.get(IChatService);481const dialogService = accessor.get(IDialogService);482483// Archive all sessions484for (const session of sessions) {485const chatModel = chatService.getSession(session.resource);486if (chatModel && !await showClearEditingSessionConfirmation(chatModel, dialogService, {487isArchiveAction: true,488titleOverride: localize('archiveSession', "Archive chat with pending edits?"),489messageOverride: localize('archiveSessionDescription', "You have pending changes in this chat session.")490})) {491return;492}493494session.setArchived(true);495}496}497}498499export class UnarchiveAgentSessionAction extends BaseAgentSessionAction {500501constructor() {502super({503id: 'agentSession.unarchive',504title: localize2('unarchive', "Unarchive"),505icon: Codicon.unarchive,506keybinding: {507primary: KeyMod.Shift | KeyCode.Delete,508mac: {509primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Backspace,510},511weight: KeybindingWeight.WorkbenchContrib + 1,512when: ContextKeyExpr.and(513ChatContextKeys.agentSessionsViewerFocused,514ChatContextKeys.isArchivedAgentSession515)516},517menu: [{518id: MenuId.AgentSessionItemToolbar,519group: 'navigation',520order: 1,521when: ChatContextKeys.isArchivedAgentSession,522}, {523id: MenuId.AgentSessionsContext,524group: '1_edit',525order: 2,526when: ChatContextKeys.isArchivedAgentSession,527}]528});529}530531runWithSessions(sessions: IAgentSession[]): void {532for (const session of sessions) {533session.setArchived(false);534}535}536}537538export class RenameAgentSessionAction extends BaseAgentSessionAction {539540constructor() {541super({542id: AGENT_SESSION_RENAME_ACTION_ID,543title: localize2('rename', "Rename..."),544precondition: ChatContextKeys.hasMultipleAgentSessionsSelected.negate(),545keybinding: {546primary: KeyCode.F2,547mac: {548primary: KeyCode.Enter549},550weight: KeybindingWeight.WorkbenchContrib + 1,551when: ContextKeyExpr.and(552ChatContextKeys.agentSessionsViewerFocused,553ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local)554),555},556menu: {557id: MenuId.AgentSessionsContext,558group: '1_edit',559order: 3,560when: ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local)561}562});563}564565async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {566const session = sessions.at(0);567if (!session) {568return;569}570571const quickInputService = accessor.get(IQuickInputService);572const chatService = accessor.get(IChatService);573574const title = await quickInputService.input({ prompt: localize('newChatTitle', "New agent session title"), value: session.label });575if (title) {576chatService.setChatSessionTitle(session.resource, title);577}578}579}580581export class DeleteAgentSessionAction extends BaseAgentSessionAction {582583constructor() {584super({585id: AGENT_SESSION_DELETE_ACTION_ID,586title: localize2('delete', "Delete..."),587menu: {588id: MenuId.AgentSessionsContext,589group: '1_edit',590order: 4,591when: ChatContextKeys.agentSessionType.isEqualTo(AgentSessionProviders.Local)592}593});594}595596async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {597if (sessions.length === 0) {598return;599}600601const chatService = accessor.get(IChatService);602const dialogService = accessor.get(IDialogService);603const widgetService = accessor.get(IChatWidgetService);604605const confirmed = await dialogService.confirm({606message: sessions.length === 1607? localize('deleteSession.confirm', "Are you sure you want to delete this chat session?")608: localize('deleteSessions.confirm', "Are you sure you want to delete {0} chat sessions?", sessions.length),609detail: localize('deleteSession.detail', "This action cannot be undone."),610primaryButton: localize('deleteSession.delete', "Delete")611});612613if (!confirmed.confirmed) {614return;615}616617for (const session of sessions) {618619// Clear chat widget620await widgetService.getWidgetBySessionResource(session.resource)?.clear();621622// Remove from storage623await chatService.removeHistoryEntry(session.resource);624}625}626}627628export class DeleteAllLocalSessionsAction extends Action2 {629630constructor() {631super({632id: 'workbench.action.chat.clearHistory',633title: localize2('agentSessions.deleteAll', "Delete All Local Workspace Chat Sessions"),634precondition: ChatContextKeys.enabled,635category: AGENT_SESSIONS_CATEGORY,636f1: true,637});638}639640async run(accessor: ServicesAccessor, ...args: unknown[]) {641const chatService = accessor.get(IChatService);642const widgetService = accessor.get(IChatWidgetService);643const dialogService = accessor.get(IDialogService);644const agentSessionsService = accessor.get(IAgentSessionsService);645646const localSessionsCount = agentSessionsService.model.sessions.filter(session => isLocalAgentSessionItem(session)).length;647if (localSessionsCount === 0) {648return;649}650651const confirmed = await dialogService.confirm({652message: localSessionsCount === 1653? localize('deleteAllChats.confirmSingle', "Are you sure you want to delete 1 local workspace chat session?")654: localize('deleteAllChats.confirm', "Are you sure you want to delete {0} local workspace chat sessions?", localSessionsCount),655detail: localize('deleteAllChats.detail', "This action cannot be undone."),656primaryButton: localize('deleteAllChats.button', "Delete All")657});658659if (!confirmed.confirmed) {660return;661}662663// Clear all chat widgets664await Promise.all(widgetService.getAllWidgets().map(widget => widget.clear()));665666// Remove from storage667await chatService.clearAllHistoryEntries();668}669}670671abstract class BaseOpenAgentSessionAction extends BaseAgentSessionAction {672673async runWithSessions(sessions: IAgentSession[], accessor: ServicesAccessor): Promise<void> {674const chatWidgetService = accessor.get(IChatWidgetService);675676const targetGroup = this.getTargetGroup();677for (const session of sessions) {678const uri = session.resource;679680await chatWidgetService.openSession(uri, targetGroup, {681...this.getOptions(),682pinned: true683});684}685}686687protected abstract getTargetGroup(): PreferredGroup;688689protected abstract getOptions(): IChatEditorOptions;690}691692export class OpenAgentSessionInEditorGroupAction extends BaseOpenAgentSessionAction {693694static readonly id = 'workbench.action.chat.openSessionInEditorGroup';695696constructor() {697super({698id: OpenAgentSessionInEditorGroupAction.id,699title: localize2('chat.openSessionInEditorGroup.label', "Open as Editor"),700keybinding: {701primary: KeyMod.CtrlCmd | KeyCode.Enter,702mac: {703primary: KeyMod.WinCtrl | KeyCode.Enter704},705weight: KeybindingWeight.WorkbenchContrib + 1,706when: ChatContextKeys.agentSessionsViewerFocused,707},708menu: {709id: MenuId.AgentSessionsContext,710order: 1,711group: 'navigation'712}713});714}715716protected getTargetGroup(): PreferredGroup {717return ACTIVE_GROUP;718}719720protected getOptions(): IChatEditorOptions {721return {};722}723}724725export class OpenAgentSessionInNewEditorGroupAction extends BaseOpenAgentSessionAction {726727static readonly id = 'workbench.action.chat.openSessionInNewEditorGroup';728729constructor() {730super({731id: OpenAgentSessionInNewEditorGroupAction.id,732title: localize2('chat.openSessionInNewEditorGroup.label', "Open to the Side"),733keybinding: {734primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter,735mac: {736primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter737},738weight: KeybindingWeight.WorkbenchContrib + 1,739when: ChatContextKeys.agentSessionsViewerFocused,740},741menu: {742id: MenuId.AgentSessionsContext,743order: 2,744group: 'navigation'745}746});747}748749protected getTargetGroup(): PreferredGroup {750return SIDE_GROUP;751}752753protected getOptions(): IChatEditorOptions {754return {};755}756}757758export class OpenAgentSessionInNewWindowAction extends BaseOpenAgentSessionAction {759760static readonly id = 'workbench.action.chat.openSessionInNewWindow';761762constructor() {763super({764id: OpenAgentSessionInNewWindowAction.id,765title: localize2('chat.openSessionInNewWindow.label', "Open in New Window"),766menu: {767id: MenuId.AgentSessionsContext,768order: 3,769group: 'navigation'770}771});772}773774protected getTargetGroup(): PreferredGroup {775return AUX_WINDOW_GROUP;776}777778protected getOptions(): IChatEditorOptions {779return {780auxiliary: { compact: true, bounds: { width: 800, height: 640 } }781};782}783}784785//#endregion786787//#region Agent Sessions Sidebar788789export class RefreshAgentSessionsViewerAction extends Action2 {790791constructor() {792super({793id: 'agentSessionsViewer.refresh',794title: localize2('refresh', "Refresh Agent Sessions"),795icon: Codicon.refresh,796menu: {797id: MenuId.AgentSessionsToolbar,798group: 'navigation',799order: 1,800},801});802}803804override run(accessor: ServicesAccessor, agentSessionsControl: IAgentSessionsControl) {805agentSessionsControl.refresh();806}807}808809export class FindAgentSessionInViewerAction extends Action2 {810811constructor() {812super({813id: 'agentSessionsViewer.find',814title: localize2('find', "Find Agent Session"),815icon: Codicon.search,816menu: {817id: MenuId.AgentSessionsToolbar,818group: 'navigation',819order: 2,820}821});822}823824override run(accessor: ServicesAccessor, agentSessionsControl: IAgentSessionsControl) {825return agentSessionsControl.openFind();826}827}828829abstract class UpdateChatViewWidthAction extends Action2 {830831async run(accessor: ServicesAccessor): Promise<void> {832const layoutService = accessor.get(IWorkbenchLayoutService);833const viewDescriptorService = accessor.get(IViewDescriptorService);834const configurationService = accessor.get(IConfigurationService);835const viewsService = accessor.get(IViewsService);836const paneCompositeService = accessor.get(IPaneCompositePartService);837838const chatLocation = viewDescriptorService.getViewLocationById(ChatViewId);839if (typeof chatLocation !== 'number') {840return; // we need a view location841}842843// Determine if we can resize the view: this is not possible844// for when the chat view is in the panel at the top or bottom845const panelPosition = layoutService.getPanelPosition();846const canResizeView = chatLocation !== ViewContainerLocation.Panel || (panelPosition === Position.LEFT || panelPosition === Position.RIGHT);847848// Update configuration if needed849const chatViewSessionsEnabled = configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsEnabled);850if (!chatViewSessionsEnabled) {851await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true);852}853854let chatView = viewsService.getActiveViewWithId<ChatViewPane>(ChatViewId);855if (!chatView) {856chatView = await viewsService.openView<ChatViewPane>(ChatViewId, false);857}858if (!chatView) {859return; // we need the chat view860}861862const configuredOrientation = configurationService.getValue<'stacked' | 'sideBySide' | unknown>(ChatConfiguration.ChatViewSessionsOrientation);863let validatedConfiguredOrientation: 'stacked' | 'sideBySide';864if (configuredOrientation === 'stacked' || configuredOrientation === 'sideBySide') {865validatedConfiguredOrientation = configuredOrientation;866} else {867validatedConfiguredOrientation = 'sideBySide'; // default868}869870const newOrientation = this.getOrientation();871const lastWidthForOrientation = chatView?.getLastDimensions(newOrientation)?.width;872873if ((!canResizeView || validatedConfiguredOrientation === 'sideBySide') && newOrientation === AgentSessionsViewerOrientation.Stacked) {874chatView.updateConfiguredSessionsViewerOrientation('stacked');875} else if ((!canResizeView || validatedConfiguredOrientation === 'stacked') && newOrientation === AgentSessionsViewerOrientation.SideBySide) {876chatView.updateConfiguredSessionsViewerOrientation('sideBySide');877}878879if (!canResizeView) {880return; // location does not allow for resize (panel top or bottom)881}882883const part = paneCompositeService.getPartId(chatLocation);884let currentSize = layoutService.getSize(part);885886const chatViewDefaultWidth = 300;887const sessionsViewDefaultWidth = chatViewDefaultWidth;888const sideBySideMinWidth = chatViewDefaultWidth + sessionsViewDefaultWidth + 1; // account for possible theme border889890if (891(newOrientation === AgentSessionsViewerOrientation.SideBySide && currentSize.width >= sideBySideMinWidth) || // already wide enough to show side by side892(newOrientation === AgentSessionsViewerOrientation.Stacked && chatLocation === ViewContainerLocation.AuxiliaryBar && layoutService.isAuxiliaryBarMaximized()) // try to not leave maximized state if maximized893) {894return;895}896897// Leave maximized state if applicable898if (chatLocation === ViewContainerLocation.AuxiliaryBar) {899layoutService.setAuxiliaryBarMaximized(false);900currentSize = layoutService.getSize(part);901}902903// Figure out the right new width904let newWidth: number;905if (newOrientation === AgentSessionsViewerOrientation.SideBySide) {906newWidth = Math.max(sideBySideMinWidth, lastWidthForOrientation || Math.round(layoutService.mainContainerDimension.width / 2));907} else {908newWidth = lastWidthForOrientation || Math.max(chatViewDefaultWidth, currentSize.width - sessionsViewDefaultWidth);909}910911// Apply the new width912layoutService.setSize(part, { width: newWidth, height: currentSize.height });913914// If we figure out that the width was not applied due to constraints (such as window dimensions),915// we maximize the auxiliary bar to ensure the side by side experience is optimal916const actualSize = layoutService.getSize(part);917if (918chatLocation === ViewContainerLocation.AuxiliaryBar && // only applicable for auxiliary bar919newOrientation === AgentSessionsViewerOrientation.SideBySide && // only applicable when going to side by side920actualSize.width < sideBySideMinWidth // width is still not enough for side by side921) {922layoutService.setAuxiliaryBarMaximized(true);923}924}925926abstract getOrientation(): AgentSessionsViewerOrientation;927}928929export class ShowAgentSessionsSidebar extends UpdateChatViewWidthAction {930931static readonly ID = 'agentSessions.showAgentSessionsSidebar';932static readonly TITLE = localize2('showAgentSessionsSidebar', "Show Agent Sessions Sidebar");933934constructor() {935super({936id: ShowAgentSessionsSidebar.ID,937title: ShowAgentSessionsSidebar.TITLE,938precondition: ContextKeyExpr.and(939ChatContextKeys.enabled,940ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.Stacked),941),942f1: true,943category: AGENT_SESSIONS_CATEGORY,944});945}946947override getOrientation(): AgentSessionsViewerOrientation {948return AgentSessionsViewerOrientation.SideBySide;949}950}951952export class HideAgentSessionsSidebar extends UpdateChatViewWidthAction {953954static readonly ID = 'agentSessions.hideAgentSessionsSidebar';955static readonly TITLE = localize2('hideAgentSessionsSidebar', "Hide Agent Sessions Sidebar");956957constructor() {958super({959id: HideAgentSessionsSidebar.ID,960title: HideAgentSessionsSidebar.TITLE,961precondition: ContextKeyExpr.and(962ChatContextKeys.enabled,963ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.SideBySide),964),965f1: true,966category: AGENT_SESSIONS_CATEGORY,967});968}969970override getOrientation(): AgentSessionsViewerOrientation {971return AgentSessionsViewerOrientation.Stacked;972}973}974975export class ToggleAgentSessionsSidebar extends Action2 {976977static readonly ID = 'agentSessions.toggleAgentSessionsSidebar';978static readonly TITLE = localize2('toggleAgentSessionsSidebar', "Toggle Agent Sessions Sidebar");979980constructor() {981super({982id: ToggleAgentSessionsSidebar.ID,983title: ToggleAgentSessionsSidebar.TITLE,984precondition: ChatContextKeys.enabled,985f1: true,986category: AGENT_SESSIONS_CATEGORY,987});988}989990async run(accessor: ServicesAccessor): Promise<void> {991const commandService = accessor.get(ICommandService);992const viewsService = accessor.get(IViewsService);993994const chatView = viewsService.getActiveViewWithId<ChatViewPane>(ChatViewId);995const currentOrientation = chatView?.getSessionsViewerOrientation();996997if (currentOrientation === AgentSessionsViewerOrientation.SideBySide) {998await commandService.executeCommand(HideAgentSessionsSidebar.ID);999} else {1000await commandService.executeCommand(ShowAgentSessionsSidebar.ID);1001}1002}1003}10041005export class FocusAgentSessionsAction extends Action2 {10061007static readonly id = 'workbench.action.chat.focusAgentSessionsViewer';10081009constructor() {1010super({1011id: FocusAgentSessionsAction.id,1012title: localize2('chat.focusAgentSessionsViewer.label', "Focus Agent Sessions"),1013precondition: ContextKeyExpr.and(1014ChatContextKeys.enabled,1015ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true)1016),1017category: AGENT_SESSIONS_CATEGORY,1018f1: true,1019});1020}10211022async run(accessor: ServicesAccessor): Promise<void> {1023const viewsService = accessor.get(IViewsService);1024const configurationService = accessor.get(IConfigurationService);1025const commandService = accessor.get(ICommandService);10261027const chatView = await viewsService.openView<ChatViewPane>(ChatViewId, true);1028const focused = chatView?.focusSessions();1029if (focused) {1030return;1031}10321033const configuredSessionsViewerOrientation = configurationService.getValue<'stacked' | 'sideBySide' | unknown>(ChatConfiguration.ChatViewSessionsOrientation);1034if (configuredSessionsViewerOrientation === 'stacked') {1035await commandService.executeCommand(ACTION_ID_NEW_CHAT);1036} else {1037await commandService.executeCommand(ShowAgentSessionsSidebar.ID);1038}10391040chatView?.focusSessions();1041}1042}10431044//#endregion104510461047