diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts index 16ed35cb16b..4cc9edf8ac9 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.ts @@ -15,7 +15,8 @@ export enum AgentSessionProviders { Local = localChatSessionType, Background = 'copilotcli', Cloud = 'copilot-cloud-agent', - ClaudeCode = 'claude-code', + Claude = 'claude-code', + Codex = 'openai-codex', } export function getAgentSessionProvider(sessionResource: URI | string): AgentSessionProviders | undefined { @@ -24,7 +25,8 @@ export function getAgentSessionProvider(sessionResource: URI | string): AgentSes case AgentSessionProviders.Local: case AgentSessionProviders.Background: case AgentSessionProviders.Cloud: - case AgentSessionProviders.ClaudeCode: + case AgentSessionProviders.Claude: + case AgentSessionProviders.Codex: return type; default: return undefined; @@ -39,8 +41,10 @@ export function getAgentSessionProviderName(provider: AgentSessionProviders): st return localize('chat.session.providerLabel.background', "Background"); case AgentSessionProviders.Cloud: return localize('chat.session.providerLabel.cloud', "Cloud"); - case AgentSessionProviders.ClaudeCode: - return localize('chat.session.providerLabel.claude', "Claude"); + case AgentSessionProviders.Claude: + return 'Claude'; + case AgentSessionProviders.Codex: + return 'Codex'; } } @@ -52,7 +56,8 @@ export function getAgentSessionProviderIcon(provider: AgentSessionProviders): Th return Codicon.worktree; case AgentSessionProviders.Cloud: return Codicon.cloud; - case AgentSessionProviders.ClaudeCode: + case AgentSessionProviders.Codex: + case AgentSessionProviders.Claude: return Codicon.code; } } @@ -63,7 +68,20 @@ export function isFirstPartyAgentSessionProvider(provider: AgentSessionProviders case AgentSessionProviders.Background: case AgentSessionProviders.Cloud: return true; - case AgentSessionProviders.ClaudeCode: + case AgentSessionProviders.Claude: + case AgentSessionProviders.Codex: + return false; + } +} + +export function getAgentCanContinueIn(provider: AgentSessionProviders): boolean { + switch (provider) { + case AgentSessionProviders.Local: + case AgentSessionProviders.Background: + case AgentSessionProviders.Cloud: + return true; + case AgentSessionProviders.Claude: + case AgentSessionProviders.Codex: return false; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts index 7b973bc922d..9212545a22f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts @@ -49,6 +49,7 @@ import { BugIndicatingError } from '../../../../../base/common/errors.js'; import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; import { LocalChatSessionUri } from '../../common/model/chatUri.js'; import { assertNever } from '../../../../../base/common/assert.js'; +import { ICommandService } from '../../../../../platform/commands/common/commands.js'; const extensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'chatSessions', @@ -516,12 +517,18 @@ export class ChatSessionsService extends Disposable implements IChatSessionsServ const disposables = new DisposableStore(); // Mirror all create submenu actions into the global Chat New menu - for (const action of menuActions) { + for (let i = 0; i < menuActions.length; i++) { + const action = menuActions[i]; if (action instanceof MenuItemAction) { - disposables.add(MenuRegistry.appendMenuItem(MenuId.ChatNewMenu, { - command: action.item, - group: '4_externally_contributed', - })); + // TODO: This is an odd way to do this, but the best we can do currently + if (i === 0 && !contribution.canDelegate) { + disposables.add(registerNewSessionExternalAction(contribution.type, contribution.displayName, action.item.id)); + } else { + disposables.add(MenuRegistry.appendMenuItem(MenuId.ChatNewMenu, { + command: action.item, + group: '4_externally_contributed', + })); + } } } return { @@ -1125,6 +1132,24 @@ function registerNewSessionInPlaceAction(type: string, displayName: string): IDi }); } +function registerNewSessionExternalAction(type: string, displayName: string, commandId: string): IDisposable { + return registerAction2(class NewChatSessionExternalAction extends Action2 { + constructor() { + super({ + id: `workbench.action.chat.openNewChatSessionExternal.${type}`, + title: localize2('interactiveSession.openNewChatSessionExternal', "New {0}", displayName), + category: CHAT_CATEGORY, + f1: false, + precondition: ChatContextKeys.enabled, + }); + } + async run(accessor: ServicesAccessor): Promise { + const commandService = accessor.get(ICommandService); + await commandService.executeCommand(commandId); + } + }); +} + enum ChatSessionPosition { Editor = 'editor', Sidebar = 'sidebar' diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts index c5c0fc6ad2e..9e562c53e3d 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/delegationSessionPickerActionItem.ts @@ -9,7 +9,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { localize } from '../../../../../../nls.js'; import { IActionWidgetDropdownAction } from '../../../../../../platform/actionWidget/browser/actionWidgetDropdown.js'; import { ACTION_ID_NEW_CHAT } from '../../actions/chatActions.js'; -import { AgentSessionProviders, getAgentSessionProvider } from '../../agentSessions/agentSessions.js'; +import { AgentSessionProviders, getAgentCanContinueIn, getAgentSessionProvider, isFirstPartyAgentSessionProvider } from '../../agentSessions/agentSessions.js'; import { ISessionTypeItem, SessionTypePickerActionItem } from './sessionTargetPickerActionItem.js'; /** @@ -49,8 +49,19 @@ export class DelegationSessionPickerActionItem extends SessionTypePickerActionIt return this._getSelectedSessionType() !== type; // Always allow switching back to active session } + protected override _isVisible(type: AgentSessionProviders): boolean { + if (this.delegate.getActiveSessionProvider() === type) { + return true; // Always show active session type + } + + return getAgentCanContinueIn(type); + } + protected override _getSessionCategory(sessionTypeItem: ISessionTypeItem) { - return { label: localize('continueIn', "Continue In"), order: 1, showHeader: true }; + if (isFirstPartyAgentSessionProvider(sessionTypeItem.type)) { + return { label: localize('continueIn', "Continue In"), order: 1, showHeader: true }; + } + return { label: localize('continueInThirdParty', "Continue In (Third Party)"), order: 2, showHeader: false }; } protected override _getSessionDescription(sessionTypeItem: ISessionTypeItem): string | undefined { diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts b/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts index 5f5761e0e30..bc378fc83fe 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/sessionTargetPickerActionItem.ts @@ -58,6 +58,10 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { const actions: IActionWidgetDropdownAction[] = [...this._getAdditionalActions()]; for (const sessionTypeItem of this._sessionTypeItems) { + if (!this._isVisible(sessionTypeItem.type)) { + continue; + } + actions.push({ ...action, id: sessionTypeItem.commandId, @@ -134,14 +138,14 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { } private _updateAgentSessionItems(): void { - const localSessionItem = { + const localSessionItem: ISessionTypeItem = { type: AgentSessionProviders.Local, label: getAgentSessionProviderName(AgentSessionProviders.Local), description: localize('chat.sessionTarget.local.description', "Local chat session"), commandId: `workbench.action.chat.openNewChatSessionInPlace.${AgentSessionProviders.Local}`, }; - const agentSessionItems = [localSessionItem]; + const agentSessionItems: ISessionTypeItem[] = [localSessionItem]; const contributions = this.chatSessionsService.getAllChatSessionContributions(); for (const contribution of contributions) { @@ -154,12 +158,18 @@ export class SessionTypePickerActionItem extends ChatInputPickerActionViewItem { type: agentSessionType, label: getAgentSessionProviderName(agentSessionType), description: contribution.description, - commandId: `workbench.action.chat.openNewChatSessionInPlace.${contribution.type}`, + commandId: contribution.canDelegate ? + `workbench.action.chat.openNewChatSessionInPlace.${contribution.type}` : + `workbench.action.chat.openNewChatSessionExternal.${contribution.type}`, }); } this._sessionTypeItems = agentSessionItems; } + protected _isVisible(type: AgentSessionProviders): boolean { + return true; + } + protected _isSessionTypeEnabled(type: AgentSessionProviders): boolean { return true; }