Path: blob/main/extensions/copilot/test/scenarios/test-tools/workspace/chatSetup.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation and GitHub. All rights reserved.2*--------------------------------------------------------------------------------------------*/34import './media/chatSetup.css';5import { $ } from '../../../../base/browser/dom.js';6import { Dialog, DialogContentsAlignment } from '../../../../base/browser/ui/dialog/dialog.js';7import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../base/common/actions.js';8import { timeout } from '../../../../base/common/async.js';9import { CancellationToken } from '../../../../base/common/cancellation.js';10import { Codicon } from '../../../../base/common/codicons.js';11import { toErrorMessage } from '../../../../base/common/errorMessage.js';12import { isCancellationError } from '../../../../base/common/errors.js';13import { Emitter, Event } from '../../../../base/common/event.js';14import { MarkdownString } from '../../../../base/common/htmlContent.js';15import { Lazy } from '../../../../base/common/lazy.js';16import { Disposable, DisposableStore, IDisposable, markAsSingleton, MutableDisposable } from '../../../../base/common/lifecycle.js';17import Severity from '../../../../base/common/severity.js';18import { StopWatch } from '../../../../base/common/stopwatch.js';19import { equalsIgnoreCase } from '../../../../base/common/strings.js';20import { isObject } from '../../../../base/common/types.js';21import { URI } from '../../../../base/common/uri.js';22import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';23import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';24import { localize, localize2 } from '../../../../nls.js';25import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';26import { ICommandService } from '../../../../platform/commands/common/commands.js';27import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js';28import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js';29import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';30import { createWorkbenchDialogOptions } from '../../../../platform/dialogs/browser/dialog.js';31import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';32import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';33import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';34import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js';35import { ILogService } from '../../../../platform/log/common/log.js';36import { IOpenerService } from '../../../../platform/opener/common/opener.js';37import product from '../../../../platform/product/common/product.js';38import { IProductService } from '../../../../platform/product/common/productService.js';39import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js';40import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';41import { Registry } from '../../../../platform/registry/common/platform.js';42import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';43import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';44import { IWorkbenchContribution } from '../../../common/contributions.js';45import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js';46import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js';47import { AuthenticationSession, IAuthenticationService } from '../../../services/authentication/common/authentication.js';48import { ExtensionUrlHandlerOverrideRegistry } from '../../../services/extensions/browser/extensionUrlHandler.js';49import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';50import { IHostService } from '../../../services/host/browser/host.js';51import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js';52import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';53import { IViewsService } from '../../../services/views/common/viewsService.js';54import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolProgress } from '../../chat/common/languageModelToolsService.js';55import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';56import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../common/chatAgents.js';57import { ChatContextKeys } from '../common/chatContextKeys.js';58import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, ChatEntitlementService, IChatEntitlementService, isProUser } from '../common/chatEntitlementService.js';59import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestToolEntry, IChatRequestVariableData } from '../common/chatModel.js';60import { ChatRequestAgentPart, ChatRequestToolPart } from '../common/chatParserTypes.js';61import { IChatProgress, IChatService } from '../common/chatService.js';62import { ChatAgentLocation, ChatConfiguration, ChatMode, validateChatMode } from '../common/constants.js';63import { ILanguageModelsService } from '../common/languageModels.js';64import { CHAT_CATEGORY, CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from './actions/chatActions.js';65import { ChatViewId, IChatWidgetService, showCopilotView } from './chat.js';66import { CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js';67import { coalesce } from '../../../../base/common/arrays.js';6869const defaultChat = {70extensionId: product.defaultChatAgent?.extensionId ?? '',71chatExtensionId: product.defaultChatAgent?.chatExtensionId ?? '',72documentationUrl: product.defaultChatAgent?.documentationUrl ?? '',73skusDocumentationUrl: product.defaultChatAgent?.skusDocumentationUrl ?? '',74publicCodeMatchesUrl: product.defaultChatAgent?.publicCodeMatchesUrl ?? '',75manageOverageUrl: product.defaultChatAgent?.manageOverageUrl ?? '',76upgradePlanUrl: product.defaultChatAgent?.upgradePlanUrl ?? '',77providerName: product.defaultChatAgent?.providerName ?? '',78enterpriseProviderId: product.defaultChatAgent?.enterpriseProviderId ?? '',79enterpriseProviderName: product.defaultChatAgent?.enterpriseProviderName ?? '',80alternativeProviderId: product.defaultChatAgent?.alternativeProviderId ?? '',81alternativeProviderName: product.defaultChatAgent?.alternativeProviderName ?? '',82providerUriSetting: product.defaultChatAgent?.providerUriSetting ?? '',83providerScopes: product.defaultChatAgent?.providerScopes ?? [[]],84manageSettingsUrl: product.defaultChatAgent?.manageSettingsUrl ?? '',85completionsAdvancedSetting: product.defaultChatAgent?.completionsAdvancedSetting ?? '',86walkthroughCommand: product.defaultChatAgent?.walkthroughCommand ?? '',87completionsRefreshTokenCommand: product.defaultChatAgent?.completionsRefreshTokenCommand ?? '',88chatRefreshTokenCommand: product.defaultChatAgent?.chatRefreshTokenCommand ?? '',89};9091//#region Contribution9293const ToolsAgentContextKey = ContextKeyExpr.and(94ContextKeyExpr.equals(`config.${ChatConfiguration.AgentEnabled}`, true),95ChatContextKeys.Editing.agentModeDisallowed.negate(),96ContextKeyExpr.not(`previewFeaturesDisabled`) // Set by extension97);9899class SetupAgent extends Disposable implements IChatAgentImplementation {100101static registerDefaultAgents(instantiationService: IInstantiationService, location: ChatAgentLocation, mode: ChatMode | undefined, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {102return instantiationService.invokeFunction(accessor => {103const chatAgentService = accessor.get(IChatAgentService);104105let id: string;106let description = localize('chatDescription', "Ask Copilot");107switch (location) {108case ChatAgentLocation.Panel:109if (mode === ChatMode.Ask) {110id = 'setup.chat';111} else if (mode === ChatMode.Edit) {112id = 'setup.edits';113description = localize('editsDescription', "Edit files in your workspace");114} else {115id = 'setup.agent';116description = localize('agentDescription', "Edit files in your workspace in agent mode");117}118break;119case ChatAgentLocation.Terminal:120id = 'setup.terminal';121break;122case ChatAgentLocation.Editor:123id = 'setup.editor';124break;125case ChatAgentLocation.Notebook:126id = 'setup.notebook';127break;128}129130return SetupAgent.doRegisterAgent(instantiationService, chatAgentService, id, `${defaultChat.providerName} Copilot`, true, description, location, mode, context, controller);131});132}133134static registerVSCodeAgent(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {135return instantiationService.invokeFunction(accessor => {136const chatAgentService = accessor.get(IChatAgentService);137138const disposables = new DisposableStore();139140const { agent, disposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Panel, undefined, context, controller);141disposables.add(disposable);142143disposables.add(SetupTool.registerTool(instantiationService, {144id: 'setup.tools.createNewWorkspace',145source: {146type: 'internal',147},148icon: Codicon.newFolder,149displayName: localize('setupToolDisplayName', "New Workspace"),150modelDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"),151userDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"),152canBeReferencedInPrompt: true,153toolReferenceName: 'new',154when: ContextKeyExpr.true(),155}).disposable);156157return { agent, disposable: disposables };158});159}160161private static doRegisterAgent(instantiationService: IInstantiationService, chatAgentService: IChatAgentService, id: string, name: string, isDefault: boolean, description: string, location: ChatAgentLocation, mode: ChatMode | undefined, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {162const disposables = new DisposableStore();163disposables.add(chatAgentService.registerAgent(id, {164id,165name,166isDefault,167isCore: true,168modes: mode ? [mode] : [ChatMode.Ask],169when: mode === ChatMode.Agent ? ToolsAgentContextKey?.serialize() : undefined,170slashCommands: [],171disambiguation: [],172locations: [location],173metadata: { helpTextPrefix: SetupAgent.SETUP_NEEDED_MESSAGE },174description,175extensionId: nullExtensionDescription.identifier,176extensionDisplayName: nullExtensionDescription.name,177extensionPublisherId: nullExtensionDescription.publisher178}));179180const agent = disposables.add(instantiationService.createInstance(SetupAgent, context, controller, location));181disposables.add(chatAgentService.registerAgentImplementation(id, agent));182if (mode === ChatMode.Agent) {183chatAgentService.updateAgent(id, { themeIcon: Codicon.tools });184}185186return { agent, disposable: disposables };187}188189private static readonly SETUP_NEEDED_MESSAGE = new MarkdownString(localize('settingUpCopilotNeeded', "You need to set up Copilot to use Chat."));190191private readonly _onUnresolvableError = this._register(new Emitter<void>());192readonly onUnresolvableError = this._onUnresolvableError.event;193194private readonly pendingForwardedRequests = new Map<string, Promise<void>>();195196constructor(197private readonly context: ChatEntitlementContext,198private readonly controller: Lazy<ChatSetupController>,199private readonly location: ChatAgentLocation,200@IInstantiationService private readonly instantiationService: IInstantiationService,201@ILogService private readonly logService: ILogService,202@IConfigurationService private readonly configurationService: IConfigurationService,203@ITelemetryService private readonly telemetryService: ITelemetryService,204) {205super();206}207208async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void): Promise<IChatAgentResult> {209return this.instantiationService.invokeFunction(async accessor /* using accessor for lazy loading */ => {210const chatService = accessor.get(IChatService);211const languageModelsService = accessor.get(ILanguageModelsService);212const chatWidgetService = accessor.get(IChatWidgetService);213const chatAgentService = accessor.get(IChatAgentService);214const languageModelToolsService = accessor.get(ILanguageModelToolsService);215216return this.doInvoke(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);217});218}219220private async doInvoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {221if (!this.context.state.installed || this.context.state.disabled || this.context.state.entitlement === ChatEntitlement.Available || this.context.state.entitlement === ChatEntitlement.Unknown) {222return this.doInvokeWithSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);223}224225return this.doInvokeWithoutSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);226}227228private async doInvokeWithoutSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {229const requestModel = chatWidgetService.getWidgetBySessionId(request.sessionId)?.viewModel?.model.getRequests().at(-1);230if (!requestModel) {231this.logService.error('[chat setup] Request model not found, cannot redispatch request.');232return {}; // this should not happen233}234235progress({236kind: 'progressMessage',237content: new MarkdownString(localize('waitingCopilot', "Getting Copilot ready.")),238});239240await this.forwardRequestToCopilot(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);241242return {};243}244245private async forwardRequestToCopilot(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {246try {247await this.doForwardRequestToCopilot(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);248} catch (error) {249progress({250kind: 'warning',251content: new MarkdownString(localize('copilotUnavailableWarning', "Copilot failed to get a response. Please try again."))252});253}254}255256private async doForwardRequestToCopilot(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {257if (this.pendingForwardedRequests.has(requestModel.session.sessionId)) {258throw new Error('Request already in progress');259}260261const forwardRequest = this.doForwardRequestToCopilotWhenReady(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);262this.pendingForwardedRequests.set(requestModel.session.sessionId, forwardRequest);263264try {265await forwardRequest;266} finally {267this.pendingForwardedRequests.delete(requestModel.session.sessionId);268}269}270271private async doForwardRequestToCopilotWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {272const widget = chatWidgetService.getWidgetBySessionId(requestModel.session.sessionId);273const mode = widget?.input.currentMode;274const languageModel = widget?.input.currentLanguageModel;275276// We need a signal to know when we can resend the request to277// Copilot. Waiting for the registration of the agent is not278// enough, we also need a language/tools model to be available.279280const whenAgentReady = this.whenAgentReady(chatAgentService, mode);281const whenLanguageModelReady = this.whenLanguageModelReady(languageModelsService);282const whenToolsModelReady = this.whenToolsModelReady(languageModelToolsService, requestModel);283284if (whenLanguageModelReady instanceof Promise || whenAgentReady instanceof Promise || whenToolsModelReady instanceof Promise) {285const timeoutHandle = setTimeout(() => {286progress({287kind: 'progressMessage',288content: new MarkdownString(localize('waitingCopilot2', "Copilot is almost ready.")),289});290}, 10000);291292try {293const ready = await Promise.race([294timeout(20000).then(() => 'timedout'),295this.whenDefaultAgentFailed(chatService).then(() => 'error'),296Promise.allSettled([whenLanguageModelReady, whenAgentReady, whenToolsModelReady])297]);298299if (ready === 'error' || ready === 'timedout') {300progress({301kind: 'warning',302content: new MarkdownString(ready === 'timedout' ?303localize('copilotTookLongWarning', "Copilot took too long to get ready. Please review the guidance in the Chat view.") :304localize('copilotFailedWarning', "Copilot failed to get ready. Please review the guidance in the Chat view.")305)306});307308// This means Copilot is unhealthy and we cannot retry the309// request. Signal this to the outside via an event.310this._onUnresolvableError.fire();311return;312}313} finally {314clearTimeout(timeoutHandle);315}316}317318await chatService.resendRequest(requestModel, { mode, userSelectedModelId: languageModel });319}320321private whenLanguageModelReady(languageModelsService: ILanguageModelsService): Promise<unknown> | void {322for (const id of languageModelsService.getLanguageModelIds()) {323const model = languageModelsService.lookupLanguageModel(id);324if (model && model.isDefault) {325return; // we have language models!326}327}328329return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, e => e.added?.some(added => added.metadata.isDefault) ?? false));330}331332private whenToolsModelReady(languageModelToolsService: ILanguageModelToolsService, requestModel: IChatRequestModel): Promise<unknown> | void {333const needsToolsModel = requestModel.message.parts.some(part => part instanceof ChatRequestToolPart);334if (!needsToolsModel) {335return; // No tools in this request, no need to check336}337338// check that tools other than setup. and internal tools are registered.339for (const tool of languageModelToolsService.getTools()) {340if (tool.source.type !== 'internal') {341return; // we have tools!342}343}344345return Event.toPromise(Event.filter(languageModelToolsService.onDidChangeTools, () => {346for (const tool of languageModelToolsService.getTools()) {347if (tool.source.type !== 'internal') {348return true; // we have tools!349}350}351352return false; // no external tools found353}));354}355356private whenAgentReady(chatAgentService: IChatAgentService, mode: ChatMode | undefined): Promise<unknown> | void {357const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);358if (defaultAgent && !defaultAgent.isCore) {359return; // we have a default agent from an extension!360}361362return Event.toPromise(Event.filter(chatAgentService.onDidChangeAgents, () => {363const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);364return Boolean(defaultAgent && !defaultAgent.isCore);365}));366}367368private async whenDefaultAgentFailed(chatService: IChatService): Promise<void> {369return new Promise<void>(resolve => {370chatService.activateDefaultAgent(this.location).catch(() => resolve());371});372}373374private async doInvokeWithSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {375this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'chat' });376377const requestModel = chatWidgetService.getWidgetBySessionId(request.sessionId)?.viewModel?.model.getRequests().at(-1);378379const setupListener = Event.runAndSubscribe(this.controller.value.onDidChange, (() => {380switch (this.controller.value.step) {381case ChatSetupStep.SigningIn:382progress({383kind: 'progressMessage',384content: new MarkdownString(localize('setupChatSignIn2', "Signing in to {0}.", ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.enterpriseProviderId ? defaultChat.enterpriseProviderName : defaultChat.providerName)),385});386break;387case ChatSetupStep.Installing:388progress({389kind: 'progressMessage',390content: new MarkdownString(localize('installingCopilot', "Getting Copilot ready.")),391});392break;393}394}));395396let result: IChatSetupResult | undefined = undefined;397try {398result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run();399} catch (error) {400this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`);401} finally {402setupListener.dispose();403}404405// User has agreed to run the setup406if (typeof result?.success === 'boolean') {407if (result.success) {408if (result.dialogSkipped) {409progress({410kind: 'markdownContent',411content: new MarkdownString(localize('copilotSetupSuccess', "Copilot setup finished successfully."))412});413} else if (requestModel) {414let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Copilot agent...415newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Copilot tools416417await this.forwardRequestToCopilot(newRequest, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);418}419} else {420progress({421kind: 'warning',422content: new MarkdownString(localize('copilotSetupError', "Copilot setup failed."))423});424}425}426427// User has cancelled the setup428else {429progress({430kind: 'markdownContent',431content: SetupAgent.SETUP_NEEDED_MESSAGE,432});433}434435return {};436}437438private replaceAgentInRequestModel(requestModel: IChatRequestModel, chatAgentService: IChatAgentService): IChatRequestModel {439const agentPart = requestModel.message.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart);440if (!agentPart) {441return requestModel;442}443444const agentId = agentPart.agent.id.replace(/setup\./, `${defaultChat.extensionId}.`.toLowerCase());445const githubAgent = chatAgentService.getAgent(agentId);446if (!githubAgent) {447return requestModel;448}449450const newAgentPart = new ChatRequestAgentPart(agentPart.range, agentPart.editorRange, githubAgent);451452return new ChatRequestModel({453session: requestModel.session as ChatModel,454message: {455parts: requestModel.message.parts.map(part => {456if (part instanceof ChatRequestAgentPart) {457return newAgentPart;458}459return part;460}),461text: requestModel.message.text462},463variableData: requestModel.variableData,464timestamp: Date.now(),465attempt: requestModel.attempt,466confirmation: requestModel.confirmation,467locationData: requestModel.locationData,468attachedContext: requestModel.attachedContext,469isCompleteAddedRequest: requestModel.isCompleteAddedRequest,470});471}472473private replaceToolInRequestModel(requestModel: IChatRequestModel): IChatRequestModel {474const toolPart = requestModel.message.parts.find((r): r is ChatRequestToolPart => r instanceof ChatRequestToolPart);475if (!toolPart) {476return requestModel;477}478479const toolId = toolPart.toolId.replace(/setup.tools\./, `copilot_`.toLowerCase());480const newToolPart = new ChatRequestToolPart(481toolPart.range,482toolPart.editorRange,483toolPart.toolName,484toolId,485toolPart.displayName,486toolPart.icon487);488489const chatRequestToolEntry: IChatRequestToolEntry = {490id: toolId,491name: 'new',492range: toolPart.range,493kind: 'tool',494value: undefined495};496497const variableData: IChatRequestVariableData = {498variables: [chatRequestToolEntry]499};500501return new ChatRequestModel({502session: requestModel.session as ChatModel,503message: {504parts: requestModel.message.parts.map(part => {505if (part instanceof ChatRequestToolPart) {506return newToolPart;507}508return part;509}),510text: requestModel.message.text511},512variableData: variableData,513timestamp: Date.now(),514attempt: requestModel.attempt,515confirmation: requestModel.confirmation,516locationData: requestModel.locationData,517attachedContext: [chatRequestToolEntry],518isCompleteAddedRequest: requestModel.isCompleteAddedRequest,519});520}521}522523524class SetupTool extends Disposable implements IToolImpl {525526static registerTool(instantiationService: IInstantiationService, toolData: IToolData): { tool: SetupTool; disposable: IDisposable } {527return instantiationService.invokeFunction(accessor => {528const toolService = accessor.get(ILanguageModelToolsService);529530const disposables = new DisposableStore();531532disposables.add(toolService.registerToolData(toolData));533534const tool = instantiationService.createInstance(SetupTool);535disposables.add(toolService.registerToolImplementation(toolData.id, tool));536537return { tool, disposable: disposables };538});539}540541async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {542const result: IToolResult = {543content: [544{545kind: 'text',546value: ''547}548]549};550551return result;552}553554async prepareToolInvocation?(parameters: any, token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {555return undefined;556}557}558559enum ChatSetupStrategy {560Canceled = 0,561DefaultSetup = 1,562SetupWithoutEnterpriseProvider = 2,563SetupWithEnterpriseProvider = 3564}565566interface IChatSetupResult {567readonly success: boolean | undefined;568readonly dialogSkipped: boolean;569}570571class ChatSetup {572573private static instance: ChatSetup | undefined = undefined;574static getInstance(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): ChatSetup {575let instance = ChatSetup.instance;576if (!instance) {577instance = ChatSetup.instance = instantiationService.invokeFunction(accessor => {578return new ChatSetup(context, controller, instantiationService, accessor.get(ITelemetryService), accessor.get(IWorkbenchLayoutService), accessor.get(IKeybindingService), accessor.get(IChatEntitlementService), accessor.get(ILogService), accessor.get(IConfigurationService));579});580}581582return instance;583}584585private pendingRun: Promise<IChatSetupResult> | undefined = undefined;586587private skipDialogOnce = false;588589private constructor(590private readonly context: ChatEntitlementContext,591private readonly controller: Lazy<ChatSetupController>,592@IInstantiationService private readonly instantiationService: IInstantiationService,593@ITelemetryService private readonly telemetryService: ITelemetryService,594@ILayoutService private readonly layoutService: IWorkbenchLayoutService,595@IKeybindingService private readonly keybindingService: IKeybindingService,596@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,597@ILogService private readonly logService: ILogService,598@IConfigurationService private readonly configurationService: IConfigurationService599) { }600601skipDialog(): void {602this.skipDialogOnce = true;603}604605async run(): Promise<IChatSetupResult> {606if (this.pendingRun) {607return this.pendingRun;608}609610this.pendingRun = this.doRun();611612try {613return await this.pendingRun;614} finally {615this.pendingRun = undefined;616}617}618619private async doRun(): Promise<IChatSetupResult> {620const dialogSkipped = this.skipDialogOnce;621this.skipDialogOnce = false;622623let setupStrategy: ChatSetupStrategy;624if (dialogSkipped || isProUser(this.chatEntitlementService.entitlement) || this.chatEntitlementService.entitlement === ChatEntitlement.Limited) {625setupStrategy = ChatSetupStrategy.DefaultSetup; // existing pro/free users setup without a dialog626} else {627setupStrategy = await this.showDialog();628}629630if (setupStrategy === ChatSetupStrategy.DefaultSetup && ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.enterpriseProviderId) {631setupStrategy = ChatSetupStrategy.SetupWithEnterpriseProvider; // users with a configured provider go through provider setup632}633634let success = undefined;635try {636switch (setupStrategy) {637case ChatSetupStrategy.SetupWithEnterpriseProvider:638success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: true });639break;640case ChatSetupStrategy.SetupWithoutEnterpriseProvider:641success = await this.controller.value.setupWithProvider({ useEnterpriseProvider: false });642break;643case ChatSetupStrategy.DefaultSetup:644success = await this.controller.value.setup();645break;646case ChatSetupStrategy.Canceled:647this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: 'failedMaybeLater', installDuration: 0, signUpErrorCode: undefined });648break;649}650} catch (error) {651this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`);652success = false;653}654655return { success, dialogSkipped };656}657658private async showDialog(): Promise<ChatSetupStrategy> {659const disposables = new DisposableStore();660661const buttons = this.getButtons();662663const dialog = disposables.add(new Dialog(664this.layoutService.activeContainer,665this.getDialogTitle(),666buttons.map(button => button[0]),667createWorkbenchDialogOptions({668type: 'none',669icon: Codicon.copilotLarge,670alignment: DialogContentsAlignment.Vertical,671cancelId: -1, // not offered as button, but X can cancel672renderFooter: this.telemetryService.telemetryLevel !== TelemetryLevel.NONE ? footer => footer.appendChild(this.createDialogFooter(disposables)) : undefined,673buttonOptions: buttons.map(button => button[2])674}, this.keybindingService, this.layoutService)675));676677const { button } = await dialog.show();678disposables.dispose();679680return buttons[button]?.[1] ?? ChatSetupStrategy.Canceled;681}682683private getButtons(): Array<[string, ChatSetupStrategy, { renderAsLink: boolean } | undefined]> {684if (this.context.state.entitlement === ChatEntitlement.Unknown) {685const supportAlternateProvider = this.configurationService.getValue('chat.setup.signInWithAlternateProvider') === true && defaultChat.alternativeProviderId;686687if (ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.enterpriseProviderId) {688return coalesce([689[localize('continueWithProvider', "Continue with {0}", defaultChat.enterpriseProviderName), ChatSetupStrategy.SetupWithEnterpriseProvider, undefined],690supportAlternateProvider ? [localize('continueWithProvider', "Continue with {0}", defaultChat.alternativeProviderName), ChatSetupStrategy.SetupWithoutEnterpriseProvider, undefined] : undefined,691[localize('signInWithProvider', "Sign in with a {0} account", defaultChat.providerName), ChatSetupStrategy.SetupWithoutEnterpriseProvider, { renderAsLink: true }]692]);693}694695return coalesce([696[localize('continueWithProvider', "Continue with {0}", defaultChat.providerName), ChatSetupStrategy.SetupWithoutEnterpriseProvider, undefined],697supportAlternateProvider ? [localize('continueWithProvider', "Continue with {0}", defaultChat.alternativeProviderName), ChatSetupStrategy.SetupWithoutEnterpriseProvider, undefined] : undefined,698[localize('signInWithProvider', "Sign in with a {0} account", defaultChat.enterpriseProviderName), ChatSetupStrategy.SetupWithEnterpriseProvider, { renderAsLink: true }]699]);700}701702return [[localize('setupCopilotButton', "Set up Copilot"), ChatSetupStrategy.DefaultSetup, undefined]];703}704705private getDialogTitle(): string {706if (this.context.state.entitlement === ChatEntitlement.Unknown) {707return this.context.state.registered ? localize('signUp', "Sign in to use Copilot") : localize('signUpFree', "Sign in to use Copilot for free");708}709710if (isProUser(this.context.state.entitlement)) {711return localize('copilotProTitle', "Start using Copilot Pro");712}713714return this.context.state.registered ? localize('copilotTitle', "Start using Copilot") : localize('copilotFreeTitle', "Start using Copilot for free");715}716717private createDialogFooter(disposables: DisposableStore): HTMLElement {718const element = $('.chat-setup-dialog-footer');719720const markdown = this.instantiationService.createInstance(MarkdownRenderer, {});721722// SKU Settings723const settings = localize({ key: 'settings', comment: ['{Locked="["}', '{Locked="]({0})"}', '{Locked="]({1})"}'] }, "GitHub Copilot Free, Pro and Pro+ may show [public code]({0}) suggestions and we may use your data for product improvement. You can change these [settings]({1}) at any time.", defaultChat.publicCodeMatchesUrl, defaultChat.manageSettingsUrl);724element.appendChild($('p.setup-settings', undefined, disposables.add(markdown.render(new MarkdownString(settings, { isTrusted: true }))).element));725726return element;727}728}729730export class ChatSetupContribution extends Disposable implements IWorkbenchContribution {731732static readonly ID = 'workbench.contrib.chatSetup';733734constructor(735@IProductService private readonly productService: IProductService,736@IInstantiationService private readonly instantiationService: IInstantiationService,737@ICommandService private readonly commandService: ICommandService,738@ITelemetryService private readonly telemetryService: ITelemetryService,739@IChatEntitlementService chatEntitlementService: ChatEntitlementService,740@ILogService private readonly logService: ILogService,741) {742super();743744const context = chatEntitlementService.context?.value;745const requests = chatEntitlementService.requests?.value;746if (!context || !requests) {747return; // disabled748}749750const controller = new Lazy(() => this._register(this.instantiationService.createInstance(ChatSetupController, context, requests)));751752this.registerSetupAgents(context, controller);753this.registerActions(context, requests, controller);754this.registerUrlLinkHandler();755}756757private registerSetupAgents(context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): void {758const defaultAgentDisposables = markAsSingleton(new MutableDisposable()); // prevents flicker on window reload759const vscodeAgentDisposables = markAsSingleton(new MutableDisposable());760761const updateRegistration = () => {762if (!context.state.hidden && !context.state.disabled) {763764// Default Agents (always, even if installed to allow for speedy requests right on startup)765if (!defaultAgentDisposables.value) {766const disposables = defaultAgentDisposables.value = new DisposableStore();767768// Panel Agents769const panelAgentDisposables = disposables.add(new DisposableStore());770for (const mode of [ChatMode.Ask, ChatMode.Edit, ChatMode.Agent]) {771const { agent, disposable } = SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Panel, mode, context, controller);772panelAgentDisposables.add(disposable);773panelAgentDisposables.add(agent.onUnresolvableError(() => {774// An unresolvable error from our agent registrations means that775// Copilot is unhealthy for some reason. We clear our panel776// registration to give Copilot a chance to show a custom message777// to the user from the views and stop pretending as if there was778// a functional agent.779this.logService.error('[chat setup] Unresolvable error from Copilot agent registration, clearing registration.');780panelAgentDisposables.dispose();781}));782}783784// Inline Agents785disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Terminal, undefined, context, controller).disposable);786disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Notebook, undefined, context, controller).disposable);787disposables.add(SetupAgent.registerDefaultAgents(this.instantiationService, ChatAgentLocation.Editor, undefined, context, controller).disposable);788}789790// VSCode Agent + Tool (unless installed and enabled)791if (!(context.state.installed && !context.state.disabled) && !vscodeAgentDisposables.value) {792const disposables = vscodeAgentDisposables.value = new DisposableStore();793794disposables.add(SetupAgent.registerVSCodeAgent(this.instantiationService, context, controller).disposable);795}796} else {797defaultAgentDisposables.clear();798vscodeAgentDisposables.clear();799}800801if (context.state.installed && !context.state.disabled) {802vscodeAgentDisposables.clear(); // we need to do this to prevent showing duplicate agent/tool entries in the list803}804};805806this._register(Event.runAndSubscribe(context.onDidChange, () => updateRegistration()));807}808809private registerActions(context: ChatEntitlementContext, requests: ChatEntitlementRequests, controller: Lazy<ChatSetupController>): void {810const chatSetupTriggerContext = ContextKeyExpr.or(811ChatContextKeys.Setup.installed.negate(),812ChatContextKeys.Entitlement.canSignUp813);814815const CHAT_SETUP_ACTION_LABEL = localize2('triggerChatSetup', "Use AI Features with Copilot for free...");816817class ChatSetupTriggerAction extends Action2 {818819constructor() {820super({821id: CHAT_SETUP_ACTION_ID,822title: CHAT_SETUP_ACTION_LABEL,823category: CHAT_CATEGORY,824f1: true,825precondition: chatSetupTriggerContext826});827}828829override async run(accessor: ServicesAccessor, mode: ChatMode): Promise<boolean> {830const viewsService = accessor.get(IViewsService);831const layoutService = accessor.get(IWorkbenchLayoutService);832const instantiationService = accessor.get(IInstantiationService);833const dialogService = accessor.get(IDialogService);834const commandService = accessor.get(ICommandService);835const lifecycleService = accessor.get(ILifecycleService);836837await context.update({ hidden: false });838839const chatWidgetPromise = showCopilotView(viewsService, layoutService);840if (mode) {841const chatWidget = await chatWidgetPromise;842chatWidget?.input.setChatMode(mode);843}844845const setup = ChatSetup.getInstance(instantiationService, context, controller);846const { success } = await setup.run();847if (success === false && !lifecycleService.willShutdown) {848const { confirmed } = await dialogService.confirm({849type: Severity.Error,850message: localize('setupErrorDialog', "Copilot setup failed. Would you like to try again?"),851primaryButton: localize('retry', "Retry"),852});853854if (confirmed) {855return Boolean(await commandService.executeCommand(CHAT_SETUP_ACTION_ID));856}857}858859return Boolean(success);860}861}862863class ChatSetupTriggerWithoutDialogAction extends Action2 {864865constructor() {866super({867id: 'workbench.action.chat.triggerSetupWithoutDialog',868title: CHAT_SETUP_ACTION_LABEL,869precondition: chatSetupTriggerContext870});871}872873override async run(accessor: ServicesAccessor): Promise<void> {874const viewsService = accessor.get(IViewsService);875const layoutService = accessor.get(IWorkbenchLayoutService);876const instantiationService = accessor.get(IInstantiationService);877878await context.update({ hidden: false });879880const chatWidget = await showCopilotView(viewsService, layoutService);881ChatSetup.getInstance(instantiationService, context, controller).skipDialog();882chatWidget?.acceptInput(localize('setupCopilot', "Set up Copilot."));883}884}885886class ChatSetupFromAccountsAction extends Action2 {887888constructor() {889super({890id: 'workbench.action.chat.triggerSetupFromAccounts',891title: localize2('triggerChatSetupFromAccounts', "Sign in to use Copilot..."),892menu: {893id: MenuId.AccountsContext,894group: '2_copilot',895when: ContextKeyExpr.and(896ChatContextKeys.Setup.hidden.negate(),897ChatContextKeys.Setup.installed.negate(),898ChatContextKeys.Entitlement.signedOut899)900}901});902}903904override async run(accessor: ServicesAccessor): Promise<void> {905const commandService = accessor.get(ICommandService);906const telemetryService = accessor.get(ITelemetryService);907908telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'accounts' });909910return commandService.executeCommand(CHAT_SETUP_ACTION_ID);911}912}913914class ChatSetupHideAction extends Action2 {915916static readonly ID = 'workbench.action.chat.hideSetup';917static readonly TITLE = localize2('hideChatSetup', "Hide Copilot");918919constructor() {920super({921id: ChatSetupHideAction.ID,922title: ChatSetupHideAction.TITLE,923f1: true,924category: CHAT_CATEGORY,925precondition: ContextKeyExpr.and(ChatContextKeys.Setup.installed.negate(), ChatContextKeys.Setup.hidden.negate()),926menu: {927id: MenuId.ChatTitleBarMenu,928group: 'z_hide',929order: 1,930when: ChatContextKeys.Setup.installed.negate()931}932});933}934935override async run(accessor: ServicesAccessor): Promise<void> {936const viewsDescriptorService = accessor.get(IViewDescriptorService);937const layoutService = accessor.get(IWorkbenchLayoutService);938const dialogService = accessor.get(IDialogService);939940const { confirmed } = await dialogService.confirm({941message: localize('hideChatSetupConfirm', "Are you sure you want to hide Copilot?"),942detail: localize('hideChatSetupDetail', "You can restore Copilot by running the '{0}' command.", CHAT_SETUP_ACTION_LABEL.value),943primaryButton: localize('hideChatSetupButton', "Hide Copilot")944});945946if (!confirmed) {947return;948}949950const location = viewsDescriptorService.getViewLocationById(ChatViewId);951952await context.update({ hidden: true });953954if (location === ViewContainerLocation.AuxiliaryBar) {955const activeContainers = viewsDescriptorService.getViewContainersByLocation(location).filter(container => viewsDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0);956if (activeContainers.length === 0) {957layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART); // hide if there are no views in the secondary sidebar958}959}960}961}962963const windowFocusListener = this._register(new MutableDisposable());964class UpgradePlanAction extends Action2 {965constructor() {966super({967id: 'workbench.action.chat.upgradePlan',968title: localize2('managePlan', "Upgrade to Copilot Pro"),969category: localize2('chat.category', 'Chat'),970f1: true,971precondition: ContextKeyExpr.or(972ChatContextKeys.Entitlement.canSignUp,973ChatContextKeys.Entitlement.limited,974),975menu: {976id: MenuId.ChatTitleBarMenu,977group: 'a_first',978order: 1,979when: ContextKeyExpr.and(980ChatContextKeys.Entitlement.limited,981ContextKeyExpr.or(982ChatContextKeys.chatQuotaExceeded,983ChatContextKeys.completionsQuotaExceeded984)985)986}987});988}989990override async run(accessor: ServicesAccessor, from?: string): Promise<void> {991const openerService = accessor.get(IOpenerService);992const hostService = accessor.get(IHostService);993const commandService = accessor.get(ICommandService);994995openerService.open(URI.parse(defaultChat.upgradePlanUrl));996997const entitlement = context.state.entitlement;998if (!isProUser(entitlement)) {999// If the user is not yet Pro, we listen to window focus to refresh the token1000// when the user has come back to the window assuming the user signed up.1001windowFocusListener.value = hostService.onDidChangeFocus(focus => this.onWindowFocus(focus, commandService));1002}1003}10041005private async onWindowFocus(focus: boolean, commandService: ICommandService): Promise<void> {1006if (focus) {1007windowFocusListener.clear();10081009const entitlements = await requests.forceResolveEntitlement(undefined);1010if (entitlements?.entitlement && isProUser(entitlements?.entitlement)) {1011refreshTokens(commandService);1012}1013}1014}1015}10161017class ManageAdditionalSpendAction extends Action2 {1018constructor() {1019super({1020id: 'workbench.action.chat.manageAdditionalSpend',1021title: localize2('manageAdditionalSpend', "Manage Copilot Additional Spend"),1022category: localize2('chat.category', 'Chat'),1023f1: true,1024precondition: ContextKeyExpr.or(1025ChatContextKeys.Entitlement.pro,1026ChatContextKeys.Entitlement.proPlus,1027),1028menu: {1029id: MenuId.ChatTitleBarMenu,1030group: 'a_first',1031order: 1,1032when: ContextKeyExpr.and(1033ContextKeyExpr.or(1034ChatContextKeys.Entitlement.pro,1035ChatContextKeys.Entitlement.proPlus,1036),1037ContextKeyExpr.or(1038ChatContextKeys.chatQuotaExceeded,1039ChatContextKeys.completionsQuotaExceeded1040)1041)1042}1043});1044}10451046override async run(accessor: ServicesAccessor, from?: string): Promise<void> {1047const openerService = accessor.get(IOpenerService);1048openerService.open(URI.parse(defaultChat.manageOverageUrl));1049}1050}10511052registerAction2(ChatSetupTriggerAction);1053registerAction2(ChatSetupFromAccountsAction);1054registerAction2(ChatSetupTriggerWithoutDialogAction);1055registerAction2(ChatSetupHideAction);1056registerAction2(UpgradePlanAction);1057registerAction2(ManageAdditionalSpendAction);1058}10591060private registerUrlLinkHandler(): void {1061this._register(ExtensionUrlHandlerOverrideRegistry.registerHandler({1062canHandleURL: url => {1063return url.scheme === this.productService.urlProtocol && equalsIgnoreCase(url.authority, defaultChat.chatExtensionId);1064},1065handleURL: async url => {1066const params = new URLSearchParams(url.query);1067this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'url', detail: params.get('referrer') ?? undefined });10681069await this.commandService.executeCommand(CHAT_SETUP_ACTION_ID, validateChatMode(params.get('mode')));10701071return true;1072}1073}));1074}1075}10761077//#endregion10781079//#region Setup Controller10801081type InstallChatClassification = {1082owner: 'bpasero';1083comment: 'Provides insight into chat installation.';1084installResult: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the extension was installed successfully, cancelled or failed to install.' };1085installDuration: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The duration it took to install the extension.' };1086signUpErrorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The error code in case of an error signing up.' };1087};1088type InstallChatEvent = {1089installResult: 'installed' | 'alreadyInstalled' | 'cancelled' | 'failedInstall' | 'failedNotSignedIn' | 'failedSignUp' | 'failedNotTrusted' | 'failedNoSession' | 'failedMaybeLater';1090installDuration: number;1091signUpErrorCode: number | undefined;1092};10931094enum ChatSetupStep {1095Initial = 1,1096SigningIn,1097Installing1098}10991100class ChatSetupController extends Disposable {11011102private readonly _onDidChange = this._register(new Emitter<void>());1103readonly onDidChange = this._onDidChange.event;11041105private _step = ChatSetupStep.Initial;1106get step(): ChatSetupStep { return this._step; }11071108constructor(1109private readonly context: ChatEntitlementContext,1110private readonly requests: ChatEntitlementRequests,1111@ITelemetryService private readonly telemetryService: ITelemetryService,1112@IAuthenticationService private readonly authenticationService: IAuthenticationService,1113@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,1114@IProductService private readonly productService: IProductService,1115@ILogService private readonly logService: ILogService,1116@IProgressService private readonly progressService: IProgressService,1117@IActivityService private readonly activityService: IActivityService,1118@ICommandService private readonly commandService: ICommandService,1119@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,1120@IDialogService private readonly dialogService: IDialogService,1121@IConfigurationService private readonly configurationService: IConfigurationService,1122@ILifecycleService private readonly lifecycleService: ILifecycleService,1123@IQuickInputService private readonly quickInputService: IQuickInputService,1124) {1125super();11261127this.registerListeners();1128}11291130private registerListeners(): void {1131this._register(this.context.onDidChange(() => this._onDidChange.fire()));1132}11331134private setStep(step: ChatSetupStep): void {1135if (this._step === step) {1136return;1137}11381139this._step = step;1140this._onDidChange.fire();1141}11421143async setup(options?: { forceSignIn?: boolean }): Promise<boolean> {1144const watch = new StopWatch(false);1145const title = localize('setupChatProgress', "Getting Copilot ready...");1146const badge = this.activityService.showViewContainerActivity(CHAT_SIDEBAR_PANEL_ID, {1147badge: new ProgressBadge(() => title),1148});11491150try {1151return await this.progressService.withProgress({1152location: ProgressLocation.Window,1153command: CHAT_OPEN_ACTION_ID,1154title,1155}, () => this.doSetup(options ?? {}, watch));1156} finally {1157badge.dispose();1158}1159}11601161private async doSetup(options: { forceSignIn?: boolean }, watch: StopWatch): Promise<boolean> {1162this.context.suspend(); // reduces flicker11631164let success = false;1165try {1166const providerId = ChatEntitlementRequests.providerId(this.configurationService);1167let session: AuthenticationSession | undefined;1168let entitlement: ChatEntitlement | undefined;11691170// Entitlement Unknown or `forceSignIn`: we need to sign-in user1171if (this.context.state.entitlement === ChatEntitlement.Unknown || options.forceSignIn) {1172this.setStep(ChatSetupStep.SigningIn);1173const result = await this.signIn(providerId);1174if (!result.session) {1175this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: 'failedNotSignedIn', installDuration: watch.elapsed(), signUpErrorCode: undefined });1176return false;1177}11781179session = result.session;1180entitlement = result.entitlement;1181}11821183const trusted = await this.workspaceTrustRequestService.requestWorkspaceTrust({1184message: localize('copilotWorkspaceTrust', "Copilot is currently only supported in trusted workspaces.")1185});1186if (!trusted) {1187this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: 'failedNotTrusted', installDuration: watch.elapsed(), signUpErrorCode: undefined });1188return false;1189}11901191// Install1192this.setStep(ChatSetupStep.Installing);1193success = await this.install(session, entitlement ?? this.context.state.entitlement, providerId, watch);1194} finally {1195this.setStep(ChatSetupStep.Initial);1196this.context.resume();1197}11981199return success;1200}12011202private async signIn(providerId: string): Promise<{ session: AuthenticationSession | undefined; entitlement: ChatEntitlement | undefined }> {1203let session: AuthenticationSession | undefined;1204let entitlements;1205try {1206({ session, entitlements } = await this.requests.signIn());1207} catch (e) {1208this.logService.error(`[chat setup] signIn: error ${e}`);1209}12101211if (!session && !this.lifecycleService.willShutdown) {1212const { confirmed } = await this.dialogService.confirm({1213type: Severity.Error,1214message: localize('unknownSignInError', "Failed to sign in to {0}. Would you like to try again?", ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.enterpriseProviderId ? defaultChat.enterpriseProviderName : defaultChat.providerName),1215detail: localize('unknownSignInErrorDetail', "You must be signed in to use Copilot."),1216primaryButton: localize('retry', "Retry")1217});12181219if (confirmed) {1220return this.signIn(providerId);1221}1222}12231224return { session, entitlement: entitlements?.entitlement };1225}12261227private async install(session: AuthenticationSession | undefined, entitlement: ChatEntitlement, providerId: string, watch: StopWatch): Promise<boolean> {1228const wasRunning = this.context.state.installed && !this.context.state.disabled;1229let signUpResult: boolean | { errorCode: number } | undefined = undefined;12301231try {12321233if (1234entitlement !== ChatEntitlement.Limited && // User is not signed up to Copilot Free1235!isProUser(entitlement) && // User is not signed up for a Copilot subscription1236entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free1237) {1238if (!session) {1239try {1240session = (await this.authenticationService.getSessions(providerId)).at(0);1241} catch (error) {1242// ignore - errors can throw if a provider is not registered1243}12441245if (!session) {1246this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: 'failedNoSession', installDuration: watch.elapsed(), signUpErrorCode: undefined });1247return false; // unexpected1248}1249}12501251signUpResult = await this.requests.signUpLimited(session);12521253if (typeof signUpResult !== 'boolean' /* error */) {1254this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: 'failedSignUp', installDuration: watch.elapsed(), signUpErrorCode: signUpResult.errorCode });1255}1256}12571258await this.doInstall();1259} catch (error) {1260this.logService.error(`[chat setup] install: error ${error}`);1261this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: isCancellationError(error) ? 'cancelled' : 'failedInstall', installDuration: watch.elapsed(), signUpErrorCode: undefined });1262return false;1263}12641265if (typeof signUpResult === 'boolean') {1266this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.ChatInstall', { installResult: wasRunning && !signUpResult ? 'alreadyInstalled' : 'installed', installDuration: watch.elapsed(), signUpErrorCode: undefined });1267}12681269if (wasRunning && signUpResult === true) {1270refreshTokens(this.commandService);1271}12721273return true;1274}12751276private async doInstall(): Promise<void> {1277let error: Error | undefined;1278try {1279await this.extensionsWorkbenchService.install(defaultChat.extensionId, {1280enable: true,1281isApplicationScoped: true, // install into all profiles1282isMachineScoped: false, // do not ask to sync1283installEverywhere: true, // install in local and remote1284installPreReleaseVersion: this.productService.quality !== 'stable'1285}, ChatViewId);1286} catch (e) {1287this.logService.error(`[chat setup] install: error ${error}`);1288error = e;1289}12901291if (error) {1292if (!this.lifecycleService.willShutdown) {1293const { confirmed } = await this.dialogService.confirm({1294type: Severity.Error,1295message: localize('unknownSetupError', "An error occurred while setting up Copilot. Would you like to try again?"),1296detail: error && !isCancellationError(error) ? toErrorMessage(error) : undefined,1297primaryButton: localize('retry', "Retry")1298});12991300if (confirmed) {1301return this.doInstall();1302}1303}13041305throw error;1306}1307}13081309async setupWithProvider(options: { useEnterpriseProvider: boolean }): Promise<boolean> {1310const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);1311registry.registerConfiguration({1312'id': 'copilot.setup',1313'type': 'object',1314'properties': {1315[defaultChat.completionsAdvancedSetting]: {1316'type': 'object',1317'properties': {1318'authProvider': {1319'type': 'string'1320}1321}1322},1323[defaultChat.providerUriSetting]: {1324'type': 'string'1325}1326}1327});13281329if (options.useEnterpriseProvider) {1330const success = await this.handleEnterpriseInstance();1331if (!success) {1332return false; // not properly configured, abort1333}1334}13351336let existingAdvancedSetting = this.configurationService.inspect(defaultChat.completionsAdvancedSetting).user?.value;1337if (!isObject(existingAdvancedSetting)) {1338existingAdvancedSetting = {};1339}13401341if (options.useEnterpriseProvider) {1342await this.configurationService.updateValue(`${defaultChat.completionsAdvancedSetting}`, {1343...existingAdvancedSetting,1344'authProvider': defaultChat.enterpriseProviderId1345}, ConfigurationTarget.USER);1346} else {1347await this.configurationService.updateValue(`${defaultChat.completionsAdvancedSetting}`, Object.keys(existingAdvancedSetting).length > 0 ? {1348...existingAdvancedSetting,1349'authProvider': undefined1350} : undefined, ConfigurationTarget.USER);1351}13521353return this.setup({ ...options, forceSignIn: true });1354}13551356private async handleEnterpriseInstance(): Promise<boolean /* success */> {1357const domainRegEx = /^[a-zA-Z\-_]+$/;1358const fullUriRegEx = /^(https:\/\/)?([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.ghe\.com\/?$/;13591360const uri = this.configurationService.getValue<string>(defaultChat.providerUriSetting);1361if (typeof uri === 'string' && fullUriRegEx.test(uri)) {1362return true; // already setup with a valid URI1363}13641365let isSingleWord = false;1366const result = await this.quickInputService.input({1367prompt: localize('enterpriseInstance', "What is your {0} instance?", defaultChat.enterpriseProviderName),1368placeHolder: localize('enterpriseInstancePlaceholder', 'i.e. "octocat" or "https://octocat.ghe.com"...'),1369ignoreFocusLost: true,1370value: uri,1371validateInput: async value => {1372isSingleWord = false;1373if (!value) {1374return undefined;1375}13761377if (domainRegEx.test(value)) {1378isSingleWord = true;1379return {1380content: localize('willResolveTo', "Will resolve to {0}", `https://${value}.ghe.com`),1381severity: Severity.Info1382};1383} if (!fullUriRegEx.test(value)) {1384return {1385content: localize('invalidEnterpriseInstance', 'You must enter a valid {0} instance (i.e. "octocat" or "https://octocat.ghe.com")', defaultChat.enterpriseProviderName),1386severity: Severity.Error1387};1388}13891390return undefined;1391}1392});13931394if (!result) {1395const { confirmed } = await this.dialogService.confirm({1396type: Severity.Error,1397message: localize('enterpriseSetupError', "The provided {0} instance is invalid. Would you like to enter it again?", defaultChat.enterpriseProviderName),1398primaryButton: localize('retry', "Retry")1399});14001401if (confirmed) {1402return this.handleEnterpriseInstance();1403}14041405return false;1406}14071408let resolvedUri = result;1409if (isSingleWord) {1410resolvedUri = `https://${resolvedUri}.ghe.com`;1411} else {1412const normalizedUri = result.toLowerCase();1413const hasHttps = normalizedUri.startsWith('https://');1414if (!hasHttps) {1415resolvedUri = `https://${result}`;1416}1417}14181419await this.configurationService.updateValue(defaultChat.providerUriSetting, resolvedUri, ConfigurationTarget.USER);14201421return true;1422}1423}14241425//#endregion14261427function refreshTokens(commandService: ICommandService): void {1428// ugly, but we need to signal to the extension that entitlements changed1429commandService.executeCommand(defaultChat.completionsRefreshTokenCommand);1430commandService.executeCommand(defaultChat.chatRefreshTokenCommand);1431}143214331434