Skip to content

Commit 87d1648

Browse files
authored
Merge pull request microsoft#249720 from microsoft/roblou/okay-snake
Start supporting custom modes
2 parents c1ed063 + 9817a33 commit 87d1648

File tree

14 files changed

+281
-44
lines changed

14 files changed

+281
-44
lines changed

src/vs/platform/actions/common/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ export class MenuId {
237237
static readonly ChatInput = new MenuId('ChatInput');
238238
static readonly ChatInputSide = new MenuId('ChatInputSide');
239239
static readonly ChatModelPicker = new MenuId('ChatModelPicker');
240+
static readonly ChatModePicker = new MenuId('ChatModePicker');
240241
static readonly ChatEditingWidgetToolbar = new MenuId('ChatEditingWidgetToolbar');
241242
static readonly ChatEditingEditorContent = new MenuId('ChatEditingEditorContent');
242243
static readonly ChatEditingEditorHunk = new MenuId('ChatEditingEditorHunk');

src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.j
1717
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
1818
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
1919
import { ChatContextKeys } from '../../common/chatContextKeys.js';
20+
import { ChatMode2, IChatMode, validateChatMode2 } from '../../common/chatModes.js';
2021
import { chatVariableLeader } from '../../common/chatParserTypes.js';
2122
import { IChatService } from '../../common/chatService.js';
22-
import { ChatAgentLocation, ChatConfiguration, ChatMode, validateChatMode } from '../../common/constants.js';
23+
import { ChatAgentLocation, ChatConfiguration, ChatMode, } from '../../common/constants.js';
2324
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
2425
import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js';
2526
import { IChatWidget, IChatWidgetService } from '../chat.js';
@@ -90,7 +91,7 @@ export class ChatSubmitAction extends SubmitAction {
9091
export const ToggleAgentModeActionId = 'workbench.action.chat.toggleAgentMode';
9192

9293
export interface IToggleChatModeArgs {
93-
mode: ChatMode;
94+
mode: IChatMode | ChatMode;
9495
}
9596

9697
class ToggleChatModeAction extends Action2 {
@@ -142,32 +143,32 @@ class ToggleChatModeAction extends Action2 {
142143
const arg = args.at(0) as IToggleChatModeArgs | undefined;
143144
const chatSession = context.chatWidget.viewModel?.model;
144145
const requestCount = chatSession?.getRequests().length ?? 0;
145-
const switchToMode = validateChatMode(arg?.mode) ?? this.getNextMode(context.chatWidget, requestCount, configurationService);
146+
const switchToMode = validateChatMode2(arg?.mode) ?? this.getNextMode(context.chatWidget, requestCount, configurationService);
146147

147-
if (switchToMode === context.chatWidget.input.currentMode) {
148+
if (switchToMode.id === context.chatWidget.input.currentMode2.id) {
148149
return;
149150
}
150151

151-
const chatModeCheck = await instaService.invokeFunction(handleModeSwitch, context.chatWidget.input.currentMode, switchToMode, requestCount, context.editingSession);
152+
const chatModeCheck = await instaService.invokeFunction(handleModeSwitch, context.chatWidget.input.currentMode, switchToMode.kind, requestCount, context.editingSession);
152153
if (!chatModeCheck) {
153154
return;
154155
}
155156

156-
context.chatWidget.input.setChatMode(switchToMode);
157+
context.chatWidget.input.setChatMode2(switchToMode);
157158

158159
if (chatModeCheck.needToClearSession) {
159160
await commandService.executeCommand(ACTION_ID_NEW_CHAT);
160161
}
161162
}
162163

163-
private getNextMode(chatWidget: IChatWidget, requestCount: number, configurationService: IConfigurationService): ChatMode {
164-
const modes = [ChatMode.Ask];
164+
private getNextMode(chatWidget: IChatWidget, requestCount: number, configurationService: IConfigurationService): IChatMode {
165+
const modes = [ChatMode2.Ask];
165166
if (configurationService.getValue(ChatConfiguration.Edits2Enabled) || requestCount === 0) {
166-
modes.push(ChatMode.Edit);
167+
modes.push(ChatMode2.Edit);
167168
}
168-
modes.push(ChatMode.Agent);
169+
modes.push(ChatMode2.Agent);
169170

170-
const modeIndex = modes.indexOf(chatWidget.input.currentMode);
171+
const modeIndex = modes.findIndex(mode => mode.id === chatWidget.input.currentMode2.id);
171172
const newMode = modes[(modeIndex + 1) % modes.length];
172173
return newMode;
173174
}

src/vs/workbench/contrib/chat/browser/actions/promptActions/chatModeActions.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { PromptsConfig } from '../../../../../../platform/prompts/common/config.
1212
import { PromptFilePickers } from './dialogs/askToSelectPrompt/promptFilePickers.js';
1313
import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js';
1414
import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js';
15-
import { Action2, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
15+
import { Action2, MenuId, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
1616
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
1717
import { PromptsType } from '../../../../../../platform/prompts/common/prompts.js';
1818
import { IOpenerService } from '../../../../../../platform/opener/common/opener.js';
@@ -27,10 +27,17 @@ class ManageModeAction extends Action2 {
2727
super({
2828
id: MANAGE_CUSTOM_MODE_ACTION_ID,
2929
title: localize2('manage-mode.capitalized', "Manage Custom Chat Modes..."),
30+
shortTitle: localize('manage-mode', "Manage Modes..."),
3031
icon: Codicon.bookmark,
3132
f1: true,
3233
precondition: ContextKeyExpr.and(PromptsConfig.enabledCtx, ChatContextKeys.enabled),
3334
category: CHAT_CATEGORY,
35+
menu: [
36+
{
37+
id: MenuId.ChatModePicker,
38+
when: ChatContextKeys.Modes.hasCustomChatModes
39+
}
40+
]
3441
});
3542
}
3643

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ import './promptSyntax/contributions/createPromptCommand/createPromptCommand.js'
108108
import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js';
109109
import { registerAction2 } from '../../../../platform/actions/common/actions.js';
110110
import product from '../../../../platform/product/common/product.js';
111+
import { ChatModeService, IChatModeService } from '../common/chatModes.js';
111112

112113
// Register configuration
113114
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@@ -721,6 +722,7 @@ registerSingleton(ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesSe
721722
registerSingleton(IChatEntitlementService, ChatEntitlementService, InstantiationType.Delayed);
722723
registerSingleton(IPromptsService, PromptsService, InstantiationType.Delayed);
723724
registerSingleton(IChatContextPickService, ChatContextPickService, InstantiationType.Delayed);
725+
registerSingleton(IChatModeService, ChatModeService, InstantiationType.Delayed);
724726

725727
registerWorkbenchContribution2(ChatEditingNotebookFileSystemProviderContrib.ID, ChatEditingNotebookFileSystemProviderContrib, WorkbenchPhase.BlockStartup);
726728

src/vs/workbench/contrib/chat/browser/chatInputPart.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import { ChatContextKeys } from '../common/chatContextKeys.js';
7474
import { IChatEditingSession } from '../common/chatEditingService.js';
7575
import { ChatEntitlement, IChatEntitlementService } from '../common/chatEntitlementService.js';
7676
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatModel.js';
77+
import { ChatMode2, IChatMode, validateChatMode2 } from '../common/chatModes.js';
7778
import { IChatFollowup } from '../common/chatService.js';
7879
import { IChatVariablesService } from '../common/chatVariables.js';
7980
import { IChatResponseViewModel } from '../common/chatViewModel.js';
@@ -315,11 +316,15 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
315316
private _onDidChangeCurrentChatMode: Emitter<void>;
316317
readonly onDidChangeCurrentChatMode: Event<void>;
317318

318-
private _currentMode: ChatMode;
319+
private _currentMode: IChatMode;
319320
public get currentMode(): ChatMode {
320-
return this._currentMode === ChatMode.Agent && !this.agentService.hasToolsAgent ?
321+
return this._currentMode.kind === ChatMode.Agent && !this.agentService.hasToolsAgent ?
321322
ChatMode.Edit :
322-
this._currentMode;
323+
this._currentMode.kind;
324+
}
325+
326+
public get currentMode2(): IChatMode {
327+
return this._currentMode;
323328
}
324329

325330
private cachedDimensions: dom.Dimension | undefined;
@@ -414,7 +419,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
414419
this._onDidChangeCurrentLanguageModel = this._register(new Emitter<ILanguageModelChatMetadataAndIdentifier>());
415420
this._onDidChangeCurrentChatMode = this._register(new Emitter<void>());
416421
this.onDidChangeCurrentChatMode = this._onDidChangeCurrentChatMode.event;
417-
this._currentMode = ChatMode.Ask;
422+
this._currentMode = ChatMode2.Ask;
418423
this.inputUri = URI.parse(`${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`);
419424
this._chatEditsActionsDisposables = this._register(new DisposableStore());
420425
this._chatEditsDisposables = this._register(new DisposableStore());
@@ -468,7 +473,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
468473

469474
this.initSelectedModel();
470475

471-
this._register(this.onDidChangeCurrentChatMode(() => this.accessibilityService.alert(this._currentMode)));
476+
this._register(this.onDidChangeCurrentChatMode(() => this.accessibilityService.alert(this._currentMode.kind)));
472477
this._register(this._onDidChangeCurrentLanguageModel.event(() => {
473478
if (this._currentLanguageModel?.metadata.name) {
474479
this.accessibilityService.alert(this._currentLanguageModel.metadata.name);
@@ -556,9 +561,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
556561
return;
557562
}
558563

559-
mode = validateChatMode(mode) ?? ChatMode.Ask;
564+
const mode2 = validateChatMode2(mode) ?? ChatMode2.Ask;
565+
this.setChatMode2(mode2, storeSelection);
566+
}
567+
568+
setChatMode2(mode: IChatMode, storeSelection = true): void {
569+
if (!this.options.supportsChangingModes) {
570+
return;
571+
}
572+
560573
this._currentMode = mode;
561-
this.chatMode.set(mode);
574+
this.chatMode.set(mode.kind);
562575
this._onDidChangeCurrentChatMode.fire();
563576

564577
if (storeSelection) {
@@ -678,7 +691,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
678691
}
679692
}
680693

681-
if (typeof defaultLanguageModelTreatment === 'string' && this._currentMode === ChatMode.Agent) {
694+
if (typeof defaultLanguageModelTreatment === 'string' && this._currentMode.kind === ChatMode.Agent) {
682695
this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
683696
this.logService.trace(`Applying default language model from experiment: ${defaultLanguageModelTreatment}`);
684697
this.setExpModelOrWait(defaultLanguageModelTreatment);
@@ -861,7 +874,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
861874
}
862875

863876
validateCurrentMode(): void {
864-
if (!this.agentService.hasToolsAgent && this._currentMode === ChatMode.Agent) {
877+
if (!this.agentService.hasToolsAgent && this._currentMode.kind === ChatMode.Agent) {
865878
this.setChatMode(ChatMode.Edit);
866879
}
867880
}
@@ -1079,7 +1092,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
10791092
}
10801093
} else if (action.id === ToggleAgentModeActionId && action instanceof MenuItemAction) {
10811094
const delegate: IModePickerDelegate = {
1082-
getMode: () => this.currentMode,
1095+
getMode: () => this.currentMode2,
10831096
onDidChangeMode: this._onDidChangeCurrentChatMode.event
10841097
};
10851098
return this.instantiationService.createInstance(ModePickerActionItem, action, delegate);

src/vs/workbench/contrib/chat/browser/chatSetup.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { coalesce } from '../../../../base/common/arrays.js';
7070
import { ThemeIcon } from '../../../../base/common/themables.js';
7171
import { IButton } from '../../../../base/browser/ui/button/button.js';
7272
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
73+
import { ChatMode2 } from '../common/chatModes.js';
7374

7475
const defaultChat = {
7576
extensionId: product.defaultChatAgent?.extensionId ?? '',
@@ -109,17 +110,17 @@ class SetupAgent extends Disposable implements IChatAgentImplementation {
109110
const chatAgentService = accessor.get(IChatAgentService);
110111

111112
let id: string;
112-
let description = localize('chatDescription', "Ask Copilot");
113+
let description = ChatMode2.Ask.description;
113114
switch (location) {
114115
case ChatAgentLocation.Panel:
115116
if (mode === ChatMode.Ask) {
116117
id = 'setup.chat';
117118
} else if (mode === ChatMode.Edit) {
118119
id = 'setup.edits';
119-
description = localize('editsDescription', "Edit files in your workspace");
120+
description = ChatMode2.Edit.description;
120121
} else {
121122
id = 'setup.agent';
122-
description = localize('agentDescription', "Edit files in your workspace in agent mode");
123+
description = ChatMode2.Agent.description;
123124
}
124125
break;
125126
case ChatAgentLocation.Terminal:

src/vs/workbench/contrib/chat/browser/chatWidget.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1311,7 +1311,9 @@ export class ChatWidget extends Disposable implements IChatWidget {
13111311
this.input.validateCurrentMode();
13121312

13131313
let userSelectedTools: Record<string, boolean> | undefined;
1314-
if (this.input.currentMode === ChatMode.Agent) {
1314+
if (this.input.currentMode2.customTools) {
1315+
userSelectedTools = this.toolsService.toEnablementMap(this.input.currentMode2.customTools);
1316+
} else if (this.input.currentMode === ChatMode.Agent) {
13151317
userSelectedTools = {};
13161318
for (const [tool, enablement] of this.inputPart.selectedToolsModel.asEnablementMap()) {
13171319
userSelectedTools[tool.id] = enablement;

src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class InputEditorDecorations extends Disposable {
115115
}
116116

117117
if (!inputValue) {
118-
const defaultAgent = this.chatAgentService.getDefaultAgent(this.widget.location, this.widget.input.currentMode);
118+
const description = this.widget.input.currentMode2.description;
119119
const decoration: IDecorationOptions[] = [
120120
{
121121
range: {
@@ -126,7 +126,7 @@ class InputEditorDecorations extends Disposable {
126126
},
127127
renderOptions: {
128128
after: {
129-
contentText: viewModel.inputPlaceholder || (defaultAgent?.description ?? ''),
129+
contentText: viewModel.inputPlaceholder || (description ?? ''),
130130
color: this.getPlaceholderColor()
131131
}
132132
}

src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,31 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
455455
}
456456
}
457457

458+
toEnablementMap(toolOrToolsetNames: Iterable<string>): Record<string, boolean> {
459+
const toolOrToolset = new Set<string>(toolOrToolsetNames);
460+
const result: Record<string, boolean> = {};
461+
for (const tool of this._tools.values()) {
462+
if (tool.data.toolReferenceName && toolOrToolset.has(tool.data.toolReferenceName) || toolOrToolset.has(tool.data.id)) {
463+
result[tool.data.id] = true;
464+
} else {
465+
result[tool.data.id] = false;
466+
}
467+
}
468+
469+
for (const toolSet of this._toolSets) {
470+
if (toolOrToolset.has(toolSet.toolReferenceName)) {
471+
result[toolSet.toolReferenceName] = true;
472+
}
473+
for (const tool of toolSet.getTools()) {
474+
if (toolOrToolset.has(tool.id)) {
475+
result[tool.id] = true;
476+
}
477+
}
478+
}
479+
480+
return result;
481+
}
482+
458483
private readonly _toolSets = new ObservableSet<ToolSet>();
459484

460485
readonly toolSets: IObservable<Iterable<ToolSet>> = this._toolSets.observable;

0 commit comments

Comments
 (0)