Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts
4780 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 { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js';6import { Event } from '../../../../../base/common/event.js';7import { Lazy } from '../../../../../base/common/lazy.js';8import { Disposable, DisposableStore, markAsSingleton, MutableDisposable } from '../../../../../base/common/lifecycle.js';9import Severity from '../../../../../base/common/severity.js';10import { equalsIgnoreCase } from '../../../../../base/common/strings.js';11import { URI } from '../../../../../base/common/uri.js';12import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';13import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';14import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';15import { localize, localize2 } from '../../../../../nls.js';16import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';17import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';18import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';19import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';20import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';21import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';22import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';23import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';24import { ILogService } from '../../../../../platform/log/common/log.js';25import { IMarkerService } from '../../../../../platform/markers/common/markers.js';26import { IOpenerService } from '../../../../../platform/opener/common/opener.js';27import product from '../../../../../platform/product/common/product.js';28import { IProductService } from '../../../../../platform/product/common/productService.js';29import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';30import { IWorkbenchContribution } from '../../../../common/contributions.js';31import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js';32import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../../../../services/chat/common/chatEntitlementService.js';33import { EnablementState, IWorkbenchExtensionEnablementService } from '../../../../services/extensionManagement/common/extensionManagement.js';34import { ExtensionUrlHandlerOverrideRegistry, IExtensionUrlHandlerOverride } from '../../../../services/extensions/browser/extensionUrlHandler.js';35import { IExtensionService } from '../../../../services/extensions/common/extensions.js';36import { IHostService } from '../../../../services/host/browser/host.js';37import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js';38import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';39import { IPreferencesService } from '../../../../services/preferences/common/preferences.js';40import { IExtension, IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';41import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';42import { IChatModeService } from '../../common/chatModes.js';43import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js';44import { CHAT_CATEGORY, CHAT_SETUP_ACTION_ID, CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID } from '../actions/chatActions.js';45import { ChatViewContainerId, IChatWidgetService } from '../chat.js';46import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js';47import { ChatSetupAnonymous } from './chatSetup.js';48import { ChatSetupController } from './chatSetupController.js';49import { AICodeActionsHelper, AINewSymbolNamesProvider, ChatCodeActionsProvider, SetupAgent } from './chatSetupProviders.js';50import { ChatSetup } from './chatSetupRunner.js';5152const defaultChat = {53chatExtensionId: product.defaultChatAgent?.chatExtensionId ?? '',54manageOveragesUrl: product.defaultChatAgent?.manageOverageUrl ?? '',55upgradePlanUrl: product.defaultChatAgent?.upgradePlanUrl ?? '',56completionsRefreshTokenCommand: product.defaultChatAgent?.completionsRefreshTokenCommand ?? '',57chatRefreshTokenCommand: product.defaultChatAgent?.chatRefreshTokenCommand ?? '',58};5960export class ChatSetupContribution extends Disposable implements IWorkbenchContribution {6162static readonly ID = 'workbench.contrib.chatSetup';6364constructor(65@IInstantiationService private readonly instantiationService: IInstantiationService,66@IChatEntitlementService chatEntitlementService: ChatEntitlementService,67@ILogService private readonly logService: ILogService,68@IContextKeyService private readonly contextKeyService: IContextKeyService,69@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,70@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,71@IExtensionService private readonly extensionService: IExtensionService,72@IEnvironmentService private readonly environmentService: IEnvironmentService,73) {74super();7576const context = chatEntitlementService.context?.value;77const requests = chatEntitlementService.requests?.value;78if (!context || !requests) {79return; // disabled80}8182const controller = new Lazy(() => this._register(this.instantiationService.createInstance(ChatSetupController, context, requests)));8384this.registerSetupAgents(context, controller);85this.registerActions(context, requests, controller);86this.registerUrlLinkHandler();87this.checkExtensionInstallation(context);88}8990private registerSetupAgents(context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): void {91const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload92const vscodeAgentDisposables = markAsSingleton(new MutableDisposable());9394const renameProviderDisposables = markAsSingleton(new MutableDisposable());95const codeActionsProviderDisposables = markAsSingleton(new MutableDisposable());9697const updateRegistration = () => {9899// Agent + Tools100{101if (!context.state.hidden && !context.state.disabled) {102103// Default Agents (always, even if installed to allow for speedy requests right on startup)104if (!defaultAgentDisposables.value) {105const disposables = defaultAgentDisposables.value = new DisposableStore();106107// Panel Agents108const panelAgentDisposables = disposables.add(new DisposableStore());109for (const mode of [ChatModeKind.Ask, ChatModeKind.Edit, ChatModeKind.Agent]) {110const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Chat, mode, context, controller);111panelAgentDisposables.add(disposable);112panelAgentDisposables.add(agent.onUnresolvableError(() => {113const panelAgentHasGuidance = chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when));114if (panelAgentHasGuidance) {115// An unresolvable error from our agent registrations means that116// Chat is unhealthy for some reason. We clear our panel117// registration to give Chat a chance to show a custom message118// to the user from the views and stop pretending as if there was119// a functional agent.120this.logService.error('[chat setup] Unresolvable error from Chat agent registration, clearing registration.');121panelAgentDisposables.dispose();122}123}));124}125126// Inline Agents127disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable);128disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable);129disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.EditorInline, undefined, context, controller).disposable);130}131132// Built-In Agent + Tool (unless installed, signed-in and enabled)133if ((!context.state.installed || context.state.entitlement === ChatEntitlement.Unknown || context.state.entitlement === ChatEntitlement.Unresolved) && !vscodeAgentDisposables.value) {134const disposables = vscodeAgentDisposables.value = new DisposableStore();135disposables.add(SetupAgent.registerBuiltInAgents(this.instantiationService, context, controller));136}137} else {138defaultAgentDisposables.clear();139vscodeAgentDisposables.clear();140}141142if (context.state.installed && !context.state.disabled) {143vscodeAgentDisposables.clear(); // we need to do this to prevent showing duplicate agent/tool entries in the list144}145}146147// Rename Provider148{149if (!context.state.installed && !context.state.hidden && !context.state.disabled) {150if (!renameProviderDisposables.value) {151renameProviderDisposables.value = AINewSymbolNamesProvider.registerProvider(this.instantiationService, context, controller);152}153} else {154renameProviderDisposables.clear();155}156}157158// Code Actions Provider159{160if (!context.state.installed && !context.state.hidden && !context.state.disabled) {161if (!codeActionsProviderDisposables.value) {162codeActionsProviderDisposables.value = ChatCodeActionsProvider.registerProvider(this.instantiationService);163}164} else {165codeActionsProviderDisposables.clear();166}167}168};169170this._register(Event.runAndSubscribe(context.onDidChange, () => updateRegistration()));171}172173private registerActions(context: ChatEntitlementContext, requests: ChatEntitlementRequests, controller: Lazy<ChatSetupController>): void {174175//#region Global Chat Setup Actions176177class ChatSetupTriggerAction extends Action2 {178179static CHAT_SETUP_ACTION_LABEL = localize2('triggerChatSetup', "Use AI Features with Copilot for free...");180181constructor() {182super({183id: CHAT_SETUP_ACTION_ID,184title: ChatSetupTriggerAction.CHAT_SETUP_ACTION_LABEL,185category: CHAT_CATEGORY,186f1: true,187precondition: ContextKeyExpr.or(188ChatContextKeys.Setup.hidden,189ChatContextKeys.Setup.disabled,190ChatContextKeys.Setup.untrusted,191ChatContextKeys.Setup.installed.negate(),192ChatContextKeys.Entitlement.canSignUp193)194});195}196197override async run(accessor: ServicesAccessor, mode?: ChatModeKind | string, options?: { forceSignInDialog?: boolean; additionalScopes?: readonly string[]; forceAnonymous?: ChatSetupAnonymous; inputValue?: string }): Promise<boolean> {198const widgetService = accessor.get(IChatWidgetService);199const instantiationService = accessor.get(IInstantiationService);200const dialogService = accessor.get(IDialogService);201const commandService = accessor.get(ICommandService);202const lifecycleService = accessor.get(ILifecycleService);203const configurationService = accessor.get(IConfigurationService);204205await context.update({ hidden: false });206configurationService.updateValue(ChatTeardownContribution.CHAT_DISABLED_CONFIGURATION_KEY, false);207208if (mode) {209const chatWidget = await widgetService.revealWidget();210chatWidget?.input.setChatMode(mode);211}212213if (options?.inputValue) {214const chatWidget = await widgetService.revealWidget();215chatWidget?.setInput(options.inputValue);216}217218const setup = ChatSetup.getInstance(instantiationService, context, controller);219const { success } = await setup.run(options);220if (success === false && !lifecycleService.willShutdown) {221const { confirmed } = await dialogService.confirm({222type: Severity.Error,223message: localize('setupErrorDialog', "Chat setup failed. Would you like to try again?"),224primaryButton: localize('retry', "Retry"),225});226227if (confirmed) {228return Boolean(await commandService.executeCommand(CHAT_SETUP_ACTION_ID, mode, options));229}230}231232return Boolean(success);233}234}235236class ChatSetupTriggerSupportAnonymousAction extends Action2 {237238constructor() {239super({240id: CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID,241title: ChatSetupTriggerAction.CHAT_SETUP_ACTION_LABEL242});243}244245override async run(accessor: ServicesAccessor): Promise<unknown> {246const commandService = accessor.get(ICommandService);247const telemetryService = accessor.get(ITelemetryService);248const chatEntitlementService = accessor.get(IChatEntitlementService);249250telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'api' });251252return commandService.executeCommand(CHAT_SETUP_ACTION_ID, undefined, {253forceAnonymous: chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined254});255}256}257258class ChatSetupTriggerForceSignInDialogAction extends Action2 {259260constructor() {261super({262id: 'workbench.action.chat.triggerSetupForceSignIn',263title: localize2('forceSignIn', "Sign in to use AI features")264});265}266267override async run(accessor: ServicesAccessor): Promise<unknown> {268const commandService = accessor.get(ICommandService);269const telemetryService = accessor.get(ITelemetryService);270271telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'api' });272273return commandService.executeCommand(CHAT_SETUP_ACTION_ID, undefined, { forceSignInDialog: true });274}275}276277class ChatSetupTriggerAnonymousWithoutDialogAction extends Action2 {278279constructor() {280super({281id: 'workbench.action.chat.triggerSetupAnonymousWithoutDialog',282title: ChatSetupTriggerAction.CHAT_SETUP_ACTION_LABEL283});284}285286override async run(accessor: ServicesAccessor): Promise<unknown> {287const commandService = accessor.get(ICommandService);288const telemetryService = accessor.get(ITelemetryService);289290telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'api' });291292return commandService.executeCommand(CHAT_SETUP_ACTION_ID, undefined, { forceAnonymous: ChatSetupAnonymous.EnabledWithoutDialog });293}294}295296class ChatSetupFromAccountsAction extends Action2 {297298constructor() {299super({300id: 'workbench.action.chat.triggerSetupFromAccounts',301title: localize2('triggerChatSetupFromAccounts', "Sign in to use AI features..."),302menu: {303id: MenuId.AccountsContext,304group: '2_copilot',305when: ContextKeyExpr.and(306ChatContextKeys.Setup.hidden.negate(),307ChatContextKeys.Setup.installed.negate(),308ChatContextKeys.Entitlement.signedOut309)310}311});312}313314override async run(accessor: ServicesAccessor): Promise<void> {315const commandService = accessor.get(ICommandService);316const telemetryService = accessor.get(ITelemetryService);317318telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'accounts' });319320return commandService.executeCommand(CHAT_SETUP_ACTION_ID);321}322}323324const windowFocusListener = this._register(new MutableDisposable());325class UpgradePlanAction extends Action2 {326constructor() {327super({328id: 'workbench.action.chat.upgradePlan',329title: localize2('managePlan', "Upgrade to GitHub Copilot Pro"),330category: localize2('chat.category', 'Chat'),331f1: true,332precondition: ContextKeyExpr.and(333ChatContextKeys.Setup.hidden.negate(),334ContextKeyExpr.or(335ChatContextKeys.Entitlement.canSignUp,336ChatContextKeys.Entitlement.planFree337)338),339menu: {340id: MenuId.ChatTitleBarMenu,341group: 'a_first',342order: 1,343when: ContextKeyExpr.and(344ChatContextKeys.Entitlement.planFree,345ContextKeyExpr.or(346ChatContextKeys.chatQuotaExceeded,347ChatContextKeys.completionsQuotaExceeded348)349)350}351});352}353354override async run(accessor: ServicesAccessor): Promise<void> {355const openerService = accessor.get(IOpenerService);356const hostService = accessor.get(IHostService);357const commandService = accessor.get(ICommandService);358359openerService.open(URI.parse(defaultChat.upgradePlanUrl));360361const entitlement = context.state.entitlement;362if (!isProUser(entitlement)) {363// If the user is not yet Pro, we listen to window focus to refresh the token364// when the user has come back to the window assuming the user signed up.365windowFocusListener.value = hostService.onDidChangeFocus(focus => this.onWindowFocus(focus, commandService));366}367}368369private async onWindowFocus(focus: boolean, commandService: ICommandService): Promise<void> {370if (focus) {371windowFocusListener.clear();372373const entitlements = await requests.forceResolveEntitlement(undefined);374if (entitlements?.entitlement && isProUser(entitlements?.entitlement)) {375refreshTokens(commandService);376}377}378}379}380381class EnableOveragesAction extends Action2 {382constructor() {383super({384id: 'workbench.action.chat.manageOverages',385title: localize2('manageOverages', "Manage GitHub Copilot Overages"),386category: localize2('chat.category', 'Chat'),387f1: true,388precondition: ContextKeyExpr.and(389ChatContextKeys.Setup.hidden.negate(),390ContextKeyExpr.or(391ChatContextKeys.Entitlement.planPro,392ChatContextKeys.Entitlement.planProPlus,393)394),395menu: {396id: MenuId.ChatTitleBarMenu,397group: 'a_first',398order: 1,399when: ContextKeyExpr.and(400ContextKeyExpr.or(401ChatContextKeys.Entitlement.planPro,402ChatContextKeys.Entitlement.planProPlus,403),404ContextKeyExpr.or(405ChatContextKeys.chatQuotaExceeded,406ChatContextKeys.completionsQuotaExceeded407)408)409}410});411}412413override async run(accessor: ServicesAccessor): Promise<void> {414const openerService = accessor.get(IOpenerService);415openerService.open(URI.parse(defaultChat.manageOveragesUrl));416}417}418419registerAction2(ChatSetupTriggerAction);420registerAction2(ChatSetupTriggerForceSignInDialogAction);421registerAction2(ChatSetupFromAccountsAction);422registerAction2(ChatSetupTriggerAnonymousWithoutDialogAction);423registerAction2(ChatSetupTriggerSupportAnonymousAction);424registerAction2(UpgradePlanAction);425registerAction2(EnableOveragesAction);426427//#endregion428429//#region Editor Context Menu430431function registerGenerateCodeCommand(coreCommand: 'chat.internal.explain' | 'chat.internal.fix' | 'chat.internal.review' | 'chat.internal.generateDocs' | 'chat.internal.generateTests', actualCommand: string): void {432433CommandsRegistry.registerCommand(coreCommand, async accessor => {434const commandService = accessor.get(ICommandService);435const codeEditorService = accessor.get(ICodeEditorService);436const markerService = accessor.get(IMarkerService);437438switch (coreCommand) {439case 'chat.internal.explain':440case 'chat.internal.fix': {441const textEditor = codeEditorService.getActiveCodeEditor();442const uri = textEditor?.getModel()?.uri;443const range = textEditor?.getSelection();444if (!uri || !range) {445return;446}447448const markers = AICodeActionsHelper.warningOrErrorMarkersAtRange(markerService, uri, range);449450const actualCommand = coreCommand === 'chat.internal.explain'451? AICodeActionsHelper.explainMarkers(markers)452: AICodeActionsHelper.fixMarkers(markers, range);453454await commandService.executeCommand(actualCommand.id, ...(actualCommand.arguments ?? []));455456break;457}458case 'chat.internal.review':459case 'chat.internal.generateDocs':460case 'chat.internal.generateTests': {461const result = await commandService.executeCommand(CHAT_SETUP_SUPPORT_ANONYMOUS_ACTION_ID);462if (result) {463await commandService.executeCommand(actualCommand);464}465}466}467});468}469registerGenerateCodeCommand('chat.internal.explain', 'github.copilot.chat.explain');470registerGenerateCodeCommand('chat.internal.fix', 'github.copilot.chat.fix');471registerGenerateCodeCommand('chat.internal.review', 'github.copilot.chat.review');472registerGenerateCodeCommand('chat.internal.generateDocs', 'github.copilot.chat.generateDocs');473registerGenerateCodeCommand('chat.internal.generateTests', 'github.copilot.chat.generateTests');474475const internalGenerateCodeContext = ContextKeyExpr.and(476ChatContextKeys.Setup.hidden.negate(),477ChatContextKeys.Setup.disabled.negate(),478ChatContextKeys.Setup.installed.negate(),479);480481MenuRegistry.appendMenuItem(MenuId.EditorContext, {482command: {483id: 'chat.internal.explain',484title: localize('explain', "Explain"),485},486group: '1_chat',487order: 4,488when: internalGenerateCodeContext489});490491MenuRegistry.appendMenuItem(MenuId.ChatTextEditorMenu, {492command: {493id: 'chat.internal.fix',494title: localize('fix', "Fix"),495},496group: '1_action',497order: 1,498when: ContextKeyExpr.and(499internalGenerateCodeContext,500EditorContextKeys.readOnly.negate()501)502});503504MenuRegistry.appendMenuItem(MenuId.ChatTextEditorMenu, {505command: {506id: 'chat.internal.review',507title: localize('review', "Code Review"),508},509group: '1_action',510order: 2,511when: internalGenerateCodeContext512});513514MenuRegistry.appendMenuItem(MenuId.ChatTextEditorMenu, {515command: {516id: 'chat.internal.generateDocs',517title: localize('generateDocs', "Generate Docs"),518},519group: '2_generate',520order: 1,521when: ContextKeyExpr.and(522internalGenerateCodeContext,523EditorContextKeys.readOnly.negate()524)525});526527MenuRegistry.appendMenuItem(MenuId.ChatTextEditorMenu, {528command: {529id: 'chat.internal.generateTests',530title: localize('generateTests', "Generate Tests"),531},532group: '2_generate',533order: 2,534when: ContextKeyExpr.and(535internalGenerateCodeContext,536EditorContextKeys.readOnly.negate()537)538});539}540541private registerUrlLinkHandler(): void {542this._register(ExtensionUrlHandlerOverrideRegistry.registerHandler(this.instantiationService.createInstance(ChatSetupExtensionUrlHandler)));543}544545private async checkExtensionInstallation(context: ChatEntitlementContext): Promise<void> {546547// When developing extensions, await registration and then check548if (this.environmentService.isExtensionDevelopment) {549await this.extensionService.whenInstalledExtensionsRegistered();550if (this.extensionService.extensions.find(ext => ExtensionIdentifier.equals(ext.identifier, defaultChat.chatExtensionId))) {551context.update({ installed: true, disabled: false, untrusted: false });552return;553}554}555556// Await extensions to be ready to be queried557await this.extensionsWorkbenchService.queryLocal();558559// Listen to extensions change and process extensions once560this._register(Event.runAndSubscribe<IExtension | undefined>(this.extensionsWorkbenchService.onChange, e => {561if (e && !ExtensionIdentifier.equals(e.identifier.id, defaultChat.chatExtensionId)) {562return; // unrelated event563}564565const defaultChatExtension = this.extensionsWorkbenchService.local.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.chatExtensionId));566const installed = !!defaultChatExtension?.local;567568let disabled: boolean;569let untrusted = false;570if (installed) {571disabled = !this.extensionEnablementService.isEnabled(defaultChatExtension.local);572if (disabled) {573const state = this.extensionEnablementService.getEnablementState(defaultChatExtension.local);574if (state === EnablementState.DisabledByTrustRequirement) {575disabled = false; // not disabled by user choice but576untrusted = true; // by missing workspace trust577}578}579} else {580disabled = false;581}582583context.update({ installed, disabled, untrusted });584}));585}586}587588class ChatSetupExtensionUrlHandler implements IExtensionUrlHandlerOverride {589constructor(590@IProductService private readonly productService: IProductService,591@ICommandService private readonly commandService: ICommandService,592@ITelemetryService private readonly telemetryService: ITelemetryService,593@IChatModeService private readonly chatModeService: IChatModeService,594) { }595596canHandleURL(url: URI): boolean {597return url.scheme === this.productService.urlProtocol && equalsIgnoreCase(url.authority, defaultChat.chatExtensionId);598}599600async handleURL(url: URI): Promise<boolean> {601const params = new URLSearchParams(url.query);602this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'url', detail: params.get('referrer') ?? undefined });603604const agentParam = params.get('agent') ?? params.get('mode');605const inputParam = params.get('prompt');606if (!agentParam && !inputParam) {607return false;608}609610const agentId = agentParam ? this.resolveAgentId(agentParam) : undefined;611await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, agentId, inputParam ? { inputValue: inputParam } : undefined);612return true;613}614615private resolveAgentId(agentParam: string): string | undefined {616const agents = this.chatModeService.getModes();617const allAgents = [...agents.builtin, ...agents.custom];618619const foundAgent = allAgents.find(agent => agent.id === agentParam);620if (foundAgent) {621return foundAgent.id;622}623624const nameLower = agentParam.toLowerCase();625const agentByName = allAgents.find(agent => agent.name.get().toLowerCase() === nameLower);626return agentByName?.id;627}628}629630export class ChatTeardownContribution extends Disposable implements IWorkbenchContribution {631632static readonly ID = 'workbench.contrib.chatTeardown';633634static readonly CHAT_DISABLED_CONFIGURATION_KEY = 'chat.disableAIFeatures';635636constructor(637@IChatEntitlementService chatEntitlementService: ChatEntitlementService,638@IConfigurationService private readonly configurationService: IConfigurationService,639@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,640@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,641@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,642@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService643) {644super();645646const context = chatEntitlementService.context?.value;647if (!context) {648return; // disabled649}650651this.registerListeners();652this.registerActions();653654this.handleChatDisabled(false);655}656657private handleChatDisabled(fromEvent: boolean): void {658const chatDisabled = this.configurationService.inspect(ChatTeardownContribution.CHAT_DISABLED_CONFIGURATION_KEY);659if (chatDisabled.value === true) {660this.maybeEnableOrDisableExtension(typeof chatDisabled.workspaceValue === 'boolean' ? EnablementState.DisabledWorkspace : EnablementState.DisabledGlobally);661if (fromEvent) {662this.maybeHideAuxiliaryBar();663}664} else if (chatDisabled.value === false && fromEvent /* do not enable extensions unless its an explicit settings change */) {665this.maybeEnableOrDisableExtension(typeof chatDisabled.workspaceValue === 'boolean' ? EnablementState.EnabledWorkspace : EnablementState.EnabledGlobally);666}667}668669private async registerListeners(): Promise<void> {670671// Configuration changes672this._register(this.configurationService.onDidChangeConfiguration(e => {673if (!e.affectsConfiguration(ChatTeardownContribution.CHAT_DISABLED_CONFIGURATION_KEY)) {674return;675}676677this.handleChatDisabled(true);678}));679680// Extension installation681await this.extensionsWorkbenchService.queryLocal();682this._register(this.extensionsWorkbenchService.onChange(e => {683if (e && !ExtensionIdentifier.equals(e.identifier.id, defaultChat.chatExtensionId)) {684return; // unrelated event685}686687const defaultChatExtension = this.extensionsWorkbenchService.local.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.chatExtensionId));688if (defaultChatExtension?.local && this.extensionEnablementService.isEnabled(defaultChatExtension.local)) {689this.configurationService.updateValue(ChatTeardownContribution.CHAT_DISABLED_CONFIGURATION_KEY, false);690}691}));692}693694private async maybeEnableOrDisableExtension(state: EnablementState.EnabledGlobally | EnablementState.EnabledWorkspace | EnablementState.DisabledGlobally | EnablementState.DisabledWorkspace): Promise<void> {695const defaultChatExtension = this.extensionsWorkbenchService.local.find(value => ExtensionIdentifier.equals(value.identifier.id, defaultChat.chatExtensionId));696if (!defaultChatExtension) {697return;698}699700await this.extensionsWorkbenchService.setEnablement([defaultChatExtension], state);701await this.extensionsWorkbenchService.updateRunningExtensions(state === EnablementState.EnabledGlobally || state === EnablementState.EnabledWorkspace ? localize('restartExtensionHost.reason.enable', "Enabling AI features") : localize('restartExtensionHost.reason.disable', "Disabling AI features"));702}703704private maybeHideAuxiliaryBar(): void {705const activeContainers = this.viewDescriptorService.getViewContainersByLocation(ViewContainerLocation.AuxiliaryBar).filter(706container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0707);708if (709(activeContainers.length === 0) || // chat view is already gone but we know it was there before710(activeContainers.length === 1 && activeContainers.at(0)?.id === ChatViewContainerId) // chat view is the only view which is going to go away711) {712this.layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); // hide if there are no views in the secondary sidebar713}714}715716private registerActions(): void {717718class ChatSetupHideAction extends Action2 {719720static readonly ID = 'workbench.action.chat.hideSetup';721static readonly TITLE = localize2('hideChatSetup', "Learn How to Hide AI Features");722723constructor() {724super({725id: ChatSetupHideAction.ID,726title: ChatSetupHideAction.TITLE,727f1: true,728category: CHAT_CATEGORY,729precondition: ChatContextKeys.Setup.hidden.negate(),730menu: {731id: MenuId.ChatTitleBarMenu,732group: 'z_hide',733order: 1,734when: ChatContextKeys.Setup.installed.negate()735}736});737}738739override async run(accessor: ServicesAccessor): Promise<void> {740const preferencesService = accessor.get(IPreferencesService);741742preferencesService.openSettings({ jsonEditor: false, query: `@id:${ChatTeardownContribution.CHAT_DISABLED_CONFIGURATION_KEY}` });743}744}745746registerAction2(ChatSetupHideAction);747}748}749750//#endregion751752export function refreshTokens(commandService: ICommandService): void {753// ugly, but we need to signal to the extension that entitlements changed754commandService.executeCommand(defaultChat.completionsRefreshTokenCommand);755commandService.executeCommand(defaultChat.chatRefreshTokenCommand);756}757758759