Skip to content

Commit 0fb834c

Browse files
authored
Add new agent setting and experiment (microsoft#239277)
* Contribute agent setting from vscode, with experiment to control visibility * Check agent experiment when submitting request, too * log * Fix race in test * Add separate context key mirroring the experiment, so the picker can be hidden even when the user set the setting
1 parent 6a81a59 commit 0fb834c

File tree

4 files changed

+77
-5
lines changed

4 files changed

+77
-5
lines changed

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

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import { registerEditorFeature } from '../../../../editor/common/editorFeatures.
1313
import * as nls from '../../../../nls.js';
1414
import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
1515
import { ICommandService } from '../../../../platform/commands/common/commands.js';
16-
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
16+
import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationNode, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';
1717
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
1818
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
1919
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
2020
import { Registry } from '../../../../platform/registry/common/platform.js';
2121
import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';
22-
import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';
22+
import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';
2323
import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js';
2424
import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js';
2525
import { ChatAgentLocation, ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAgentService } from '../common/chatAgents.js';
@@ -84,6 +84,10 @@ import { ChatEditorOverlayController } from './chatEditorOverlay.js';
8484
import '../common/promptSyntax/languageFeatures/promptLinkProvider.js';
8585
import { PromptFilesConfig } from '../common/promptSyntax/config.js';
8686
import { BuiltinToolsContribution } from '../common/tools/tools.js';
87+
import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js';
88+
import { IProductService } from '../../../../platform/product/common/productService.js';
89+
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
90+
import { ChatContextKeys } from '../common/chatContextKeys.js';
8791

8892
// Register configuration
8993
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@@ -217,6 +221,61 @@ class ChatResolverContribution extends Disposable {
217221
}
218222
}
219223

224+
class ChatAgentSettingContribution implements IWorkbenchContribution {
225+
226+
static readonly ID = 'workbench.contrib.chatAgentSetting';
227+
228+
private registeredNode: IConfigurationNode | undefined;
229+
230+
constructor(
231+
@IWorkbenchAssignmentService experimentService: IWorkbenchAssignmentService,
232+
@IProductService private readonly productService: IProductService,
233+
@IContextKeyService contextKeyService: IContextKeyService,
234+
) {
235+
if (this.productService.quality !== 'stable') {
236+
this.registerSetting();
237+
}
238+
239+
const expDisabledKey = ChatContextKeys.Editing.agentModeDisallowed.bindTo(contextKeyService);
240+
experimentService.getTreatment<boolean>('chatAgentEnabled').then(value => {
241+
if (value) {
242+
this.registerSetting();
243+
} else if (value === false) {
244+
this.deregisterSetting();
245+
expDisabledKey.set(true);
246+
}
247+
});
248+
}
249+
250+
private registerSetting() {
251+
if (this.registeredNode) {
252+
return;
253+
}
254+
255+
this.registeredNode = {
256+
id: 'chatAgent',
257+
title: nls.localize('interactiveSessionConfigurationTitle', "Chat"),
258+
type: 'object',
259+
properties: {
260+
'chat.agent.enabled': {
261+
type: 'boolean',
262+
description: nls.localize('chat.agent.enabled.description', "Enable agent mode for {0}. When this is enabled, a dropdown appears in the {0} view to toggle agent mode.", 'Copilot Edits'),
263+
default: this.productService.quality !== 'stable',
264+
tags: ['experimental', 'onExp'],
265+
},
266+
}
267+
};
268+
configurationRegistry.registerConfiguration(this.registeredNode);
269+
}
270+
271+
private deregisterSetting() {
272+
if (this.registeredNode) {
273+
configurationRegistry.deregisterConfigurations([this.registeredNode]);
274+
this.registeredNode = undefined;
275+
}
276+
}
277+
}
278+
220279
AccessibleViewRegistry.register(new ChatResponseAccessibleView());
221280
AccessibleViewRegistry.register(new PanelChatAccessibilityHelp());
222281
AccessibleViewRegistry.register(new QuickChatAccessibilityHelp());
@@ -334,6 +393,7 @@ registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingSta
334393
registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore);
335394
registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually);
336395
registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually);
396+
registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.BlockRestore);
337397

338398
registerChatActions();
339399
registerChatCopyActions();

src/vs/workbench/contrib/chat/common/chatContextKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,6 @@ export namespace ChatContextKeys {
8383
export const Editing = {
8484
hasToolsAgent: new RawContextKey<boolean>('chatHasToolsAgent', false, { type: 'boolean', description: localize('chatEditingHasToolsAgent', "True when a tools agent is registered.") }),
8585
agentMode: new RawContextKey<boolean>('chatAgentMode', false, { type: 'boolean', description: localize('chatEditingAgentMode', "True when edits is in agent mode.") }),
86+
agentModeDisallowed: new RawContextKey<boolean>('chatAgentModeDisallowed', false, { type: 'boolean', description: localize('chatAgentModeDisallowed', "True when agent mode is not allowed.") }), // experiment-driven disablement
8687
};
8788
}

src/vs/workbench/contrib/chat/common/chatServiceImpl.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Progress } from '../../../../platform/progress/common/progress.js';
2323
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
2424
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2525
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
26+
import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js';
2627
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
2728
import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from './chatAgents.js';
2829
import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js';
@@ -138,7 +139,8 @@ export class ChatService extends Disposable implements IChatService {
138139
@IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService,
139140
@IChatVariablesService private readonly chatVariablesService: IChatVariablesService,
140141
@IChatAgentService private readonly chatAgentService: IChatAgentService,
141-
@IConfigurationService private readonly configurationService: IConfigurationService
142+
@IConfigurationService private readonly configurationService: IConfigurationService,
143+
@IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService,
142144
) {
143145
super();
144146

@@ -687,6 +689,7 @@ export class ChatService extends Disposable implements IChatService {
687689
const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!;
688690
const command = detectedCommand ?? agentSlashCommandPart?.command;
689691
await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`);
692+
await this.checkAgentAllowed(agent);
690693

691694
// Recompute history in case the agent or command changed
692695
const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id);
@@ -811,6 +814,15 @@ export class ChatService extends Disposable implements IChatService {
811814
};
812815
}
813816

817+
private async checkAgentAllowed(agent: IChatAgentData): Promise<void> {
818+
if (agent.isToolsAgent) {
819+
const enabled = await this.experimentService.getTreatment<boolean>('chatAgentEnabled');
820+
if (enabled === false) {
821+
throw new Error('Agent is currently disabled');
822+
}
823+
}
824+
}
825+
814826
private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] {
815827
// TODO this shows why attachments still have to be cleaned up somewhat
816828
return variableData.variables.map(v => {

src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -841,8 +841,7 @@ suite('InlineChatController', function () {
841841
const newSession = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None);
842842
assertType(newSession);
843843

844-
await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor });
845-
844+
await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }))?.responseCreatedPromise;
846845

847846
assert.strictEqual(newSession.chatModel.requestInProgress, true);
848847

0 commit comments

Comments
 (0)