diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 269b6536eab8b..f570cd3641de9 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -13,13 +13,13 @@ import { registerEditorFeature } from '../../../../editor/common/editorFeatures. import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationNode, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; -import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; import { ChatAgentLocation, ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAgentService } from '../common/chatAgents.js'; @@ -84,6 +84,10 @@ import { ChatEditorOverlayController } from './chatEditorOverlay.js'; import '../common/promptSyntax/languageFeatures/promptLinkProvider.js'; import { PromptFilesConfig } from '../common/promptSyntax/config.js'; import { BuiltinToolsContribution } from '../common/tools/tools.js'; +import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -212,6 +216,61 @@ class ChatResolverContribution extends Disposable { } } +class ChatAgentSettingContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.chatAgentSetting'; + + private registeredNode: IConfigurationNode | undefined; + + constructor( + @IWorkbenchAssignmentService experimentService: IWorkbenchAssignmentService, + @IProductService private readonly productService: IProductService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + if (this.productService.quality !== 'stable') { + this.registerSetting(); + } + + const expDisabledKey = ChatContextKeys.Editing.agentModeDisallowed.bindTo(contextKeyService); + experimentService.getTreatment('chatAgentEnabled').then(value => { + if (value) { + this.registerSetting(); + } else if (value === false) { + this.deregisterSetting(); + expDisabledKey.set(true); + } + }); + } + + private registerSetting() { + if (this.registeredNode) { + return; + } + + this.registeredNode = { + id: 'chatAgent', + title: nls.localize('interactiveSessionConfigurationTitle', "Chat"), + type: 'object', + properties: { + 'chat.agent.enabled': { + type: 'boolean', + 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'), + default: this.productService.quality !== 'stable', + tags: ['experimental', 'onExp'], + }, + } + }; + configurationRegistry.registerConfiguration(this.registeredNode); + } + + private deregisterSetting() { + if (this.registeredNode) { + configurationRegistry.deregisterConfigurations([this.registeredNode]); + this.registeredNode = undefined; + } + } +} + AccessibleViewRegistry.register(new ChatResponseAccessibleView()); AccessibleViewRegistry.register(new PanelChatAccessibilityHelp()); AccessibleViewRegistry.register(new QuickChatAccessibilityHelp()); @@ -329,6 +388,7 @@ registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingSta registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually); registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.BlockRestore); registerChatActions(); registerChatCopyActions(); diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 7723d67602f1f..4a2798b9f0ebd 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -83,5 +83,6 @@ export namespace ChatContextKeys { export const Editing = { hasToolsAgent: new RawContextKey('chatHasToolsAgent', false, { type: 'boolean', description: localize('chatEditingHasToolsAgent', "True when a tools agent is registered.") }), agentMode: new RawContextKey('chatAgentMode', false, { type: 'boolean', description: localize('chatEditingAgentMode', "True when edits is in agent mode.") }), + agentModeDisallowed: new RawContextKey('chatAgentModeDisallowed', false, { type: 'boolean', description: localize('chatAgentModeDisallowed', "True when agent mode is not allowed.") }), // experiment-driven disablement }; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 6e2741852cf67..f6f352449499e 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,6 +23,7 @@ import { Progress } from '../../../../platform/progress/common/progress.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from './chatAgents.js'; 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 { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, ) { super(); @@ -687,6 +689,7 @@ export class ChatService extends Disposable implements IChatService { const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!; const command = detectedCommand ?? agentSlashCommandPart?.command; await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); + await this.checkAgentAllowed(agent); // Recompute history in case the agent or command changed const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); @@ -811,6 +814,15 @@ export class ChatService extends Disposable implements IChatService { }; } + private async checkAgentAllowed(agent: IChatAgentData): Promise { + if (agent.isToolsAgent) { + const enabled = await this.experimentService.getTreatment('chatAgentEnabled'); + if (enabled === false) { + throw new Error('Agent is currently disabled'); + } + } + } + private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] { // TODO this shows why attachments still have to be cleaned up somewhat return variableData.variables.map(v => { diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index d8d8ce7467cec..10d38f3cc79a9 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -841,8 +841,7 @@ suite('InlineChatController', function () { const newSession = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None); assertType(newSession); - await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }); - + await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }))?.responseCreatedPromise; assert.strictEqual(newSession.chatModel.requestInProgress, true);