Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts
5281 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 { timeout } from '../../../../../base/common/async.js';7import { CancellationToken } from '../../../../../base/common/cancellation.js';8import { Codicon } from '../../../../../base/common/codicons.js';9import { toErrorMessage } from '../../../../../base/common/errorMessage.js';10import { Emitter, Event } from '../../../../../base/common/event.js';11import { MarkdownString } from '../../../../../base/common/htmlContent.js';12import { Lazy } from '../../../../../base/common/lazy.js';13import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';14import { URI } from '../../../../../base/common/uri.js';15import { localize, localize2 } from '../../../../../nls.js';16import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';17import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';18import { ILogService } from '../../../../../platform/log/common/log.js';19import product from '../../../../../platform/product/common/product.js';20import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';21import { IWorkspaceTrustManagementService } from '../../../../../platform/workspace/common/workspaceTrust.js';22import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js';23import { nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';24import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolDataSource, ToolProgress } from '../../common/tools/languageModelToolsService.js';25import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../../common/participants/chatAgents.js';26import { ChatEntitlement, ChatEntitlementContext, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';27import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../../common/model/chatModel.js';28import { ChatMode } from '../../common/chatModes.js';29import { ChatRequestAgentPart, ChatRequestToolPart } from '../../common/requestParser/chatParserTypes.js';30import { IChatProgress, IChatService } from '../../common/chatService/chatService.js';31import { IChatRequestToolEntry } from '../../common/attachments/chatVariableEntries.js';32import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';33import { ILanguageModelsService } from '../../common/languageModels.js';34import { CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from '../actions/chatActions.js';35import { ChatViewId, IChatWidgetService } from '../chat.js';36import { IViewsService } from '../../../../services/views/common/viewsService.js';37import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';38import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';39import { CodeAction, CodeActionList, Command, NewSymbolName, NewSymbolNameTriggerKind } from '../../../../../editor/common/languages.js';40import { ITextModel } from '../../../../../editor/common/model.js';41import { IRange, Range } from '../../../../../editor/common/core/range.js';42import { ISelection, Selection } from '../../../../../editor/common/core/selection.js';43import { ResourceMap } from '../../../../../base/common/map.js';44import { CodeActionKind } from '../../../../../editor/contrib/codeAction/common/types.js';45import { ACTION_START as INLINE_CHAT_START } from '../../../inlineChat/common/inlineChat.js';46import { IPosition } from '../../../../../editor/common/core/position.js';47import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';48import { ChatSetupController } from './chatSetupController.js';49import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult } from './chatSetup.js';50import { ChatSetup } from './chatSetupRunner.js';51import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js';52import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';53import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js';54import { IHostService } from '../../../../services/host/browser/host.js';5556const defaultChat = {57extensionId: product.defaultChatAgent?.extensionId ?? '',58chatExtensionId: product.defaultChatAgent?.chatExtensionId ?? '',59provider: product.defaultChatAgent?.provider ?? { default: { id: '', name: '' }, enterprise: { id: '', name: '' }, apple: { id: '', name: '' }, google: { id: '', name: '' } },60outputChannelId: product.defaultChatAgent?.chatExtensionOutputId ?? '',61};6263const ToolsAgentContextKey = ContextKeyExpr.and(64ContextKeyExpr.equals(`config.${ChatConfiguration.AgentEnabled}`, true),65ContextKeyExpr.not(`previewFeaturesDisabled`) // Set by extension66);6768export class SetupAgent extends Disposable implements IChatAgentImplementation {6970static registerDefaultAgents(instantiationService: IInstantiationService, location: ChatAgentLocation, mode: ChatModeKind, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {71return instantiationService.invokeFunction(accessor => {72const chatAgentService = accessor.get(IChatAgentService);7374let description;75if (mode === ChatModeKind.Ask) {76description = ChatMode.Ask.description.get();77} else if (mode === ChatModeKind.Edit) {78description = ChatMode.Edit.description.get();79} else {80description = ChatMode.Agent.description.get();81}8283let id: string;84switch (location) {85case ChatAgentLocation.Chat:86if (mode === ChatModeKind.Ask) {87id = 'setup.chat';88} else if (mode === ChatModeKind.Edit) {89id = 'setup.edits';90} else {91id = 'setup.agent';92}93break;94case ChatAgentLocation.Terminal:95id = 'setup.terminal';96break;97case ChatAgentLocation.EditorInline:98id = 'setup.editor';99break;100case ChatAgentLocation.Notebook:101id = 'setup.notebook';102break;103}104105return SetupAgent.doRegisterAgent(instantiationService, chatAgentService, id, `${defaultChat.provider.default.name} Copilot` /* Do NOT change, this hides the username altogether in Chat */, true, description, location, mode, context, controller);106});107}108109static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): IDisposable {110return instantiationService.invokeFunction(accessor => {111const chatAgentService = accessor.get(IChatAgentService);112113const disposables = new DisposableStore();114115// Register VSCode agent116const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller);117disposables.add(vscodeDisposable);118119// Register workspace agent120const { disposable: workspaceDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.workspace', 'workspace', false, localize2('workspaceAgentDescription', "Ask about your workspace").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller);121disposables.add(workspaceDisposable);122123// Register terminal agent124const { disposable: terminalDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.terminal.agent', 'terminal', false, localize2('terminalAgentDescription', "Ask how to do something in the terminal").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller);125disposables.add(terminalDisposable);126127// Register tools128disposables.add(SetupTool.registerTool(instantiationService, {129id: 'setup_tools_createNewWorkspace',130source: ToolDataSource.Internal,131icon: Codicon.newFolder,132displayName: localize('setupToolDisplayName', "New Workspace"),133modelDescription: 'Scaffold a new workspace in VS Code',134userDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"),135canBeReferencedInPrompt: true,136toolReferenceName: 'new',137when: ContextKeyExpr.true(),138}));139140return disposables;141});142}143144private static doRegisterAgent(instantiationService: IInstantiationService, chatAgentService: IChatAgentService, id: string, name: string, isDefault: boolean, description: string, location: ChatAgentLocation, mode: ChatModeKind, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {145const disposables = new DisposableStore();146disposables.add(chatAgentService.registerAgent(id, {147id,148name,149isDefault,150isCore: true,151modes: [mode],152when: mode === ChatModeKind.Agent ? ToolsAgentContextKey?.serialize() : undefined,153slashCommands: [],154disambiguation: [],155locations: [location],156metadata: { helpTextPrefix: SetupAgent.SETUP_NEEDED_MESSAGE },157description,158extensionId: nullExtensionDescription.identifier,159extensionVersion: undefined,160extensionDisplayName: nullExtensionDescription.name,161extensionPublisherId: nullExtensionDescription.publisher162}));163164const agent = disposables.add(instantiationService.createInstance(SetupAgent, context, controller, location));165disposables.add(chatAgentService.registerAgentImplementation(id, agent));166if (mode === ChatModeKind.Agent) {167chatAgentService.updateAgent(id, { themeIcon: Codicon.tools });168}169170return { agent, disposable: disposables };171}172173private static readonly SETUP_NEEDED_MESSAGE = new MarkdownString(localize('settingUpCopilotNeeded', "You need to set up GitHub Copilot and be signed in to use Chat."));174private static readonly TRUST_NEEDED_MESSAGE = new MarkdownString(localize('trustNeeded', "You need to trust this workspace to use Chat."));175176private static readonly CHAT_RETRY_COMMAND_ID = 'workbench.action.chat.retrySetup';177178private readonly _onUnresolvableError = this._register(new Emitter<void>());179readonly onUnresolvableError = this._onUnresolvableError.event;180181private readonly pendingForwardedRequests = new ResourceMap<Promise<void>>();182183constructor(184private readonly context: ChatEntitlementContext,185private readonly controller: Lazy<ChatSetupController>,186private readonly location: ChatAgentLocation,187@IInstantiationService private readonly instantiationService: IInstantiationService,188@ILogService private readonly logService: ILogService,189@ITelemetryService private readonly telemetryService: ITelemetryService,190@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,191@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,192@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,193@IViewsService private readonly viewsService: IViewsService,194@IContextKeyService private readonly contextKeyService: IContextKeyService,195) {196super();197198this.registerCommands();199}200201private registerCommands(): void {202203// Retry chat command204this._register(CommandsRegistry.registerCommand(SetupAgent.CHAT_RETRY_COMMAND_ID, async (accessor, sessionResource: URI) => {205const hostService = accessor.get(IHostService);206const chatWidgetService = accessor.get(IChatWidgetService);207208const widget = chatWidgetService.getWidgetBySessionResource(sessionResource);209await widget?.clear();210211hostService.reload();212}));213}214215async invoke(request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void): Promise<IChatAgentResult> {216return this.instantiationService.invokeFunction(async accessor /* using accessor for lazy loading */ => {217const chatService = accessor.get(IChatService);218const languageModelsService = accessor.get(ILanguageModelsService);219const chatWidgetService = accessor.get(IChatWidgetService);220const chatAgentService = accessor.get(IChatAgentService);221const languageModelToolsService = accessor.get(ILanguageModelToolsService);222const defaultAccountService = accessor.get(IDefaultAccountService);223224return this.doInvoke(request, part => progress([part]), chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService, defaultAccountService);225});226}227228private async doInvoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService, defaultAccountService: IDefaultAccountService): Promise<IChatAgentResult> {229if (230!this.context.state.installed || // Extension not installed: run setup to install231this.context.state.disabled || // Extension disabled: run setup to enable232this.context.state.untrusted || // Workspace untrusted: run setup to ask for trust233this.context.state.entitlement === ChatEntitlement.Available || // Entitlement available: run setup to sign up234(235this.context.state.entitlement === ChatEntitlement.Unknown && // Entitlement unknown: run setup to sign in / sign up236!this.chatEntitlementService.anonymous // unless anonymous access is enabled237)238) {239return this.doInvokeWithSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService, defaultAccountService);240}241242return this.doInvokeWithoutSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);243}244245private async doInvokeWithoutSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {246const requestModel = chatWidgetService.getWidgetBySessionResource(request.sessionResource)?.viewModel?.model.getRequests().at(-1);247if (!requestModel) {248this.logService.error('[chat setup] Request model not found, cannot redispatch request.');249return {}; // this should not happen250}251252progress({253kind: 'progressMessage',254content: new MarkdownString(localize('waitingChat', "Getting chat ready...")),255});256257await this.forwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);258259return {};260}261262private async forwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {263try {264await this.doForwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);265} catch (error) {266this.logService.error('[chat setup] Failed to forward request to chat', error);267268progress({269kind: 'warning',270content: new MarkdownString(localize('copilotUnavailableWarning', "Failed to get a response. Please try again."))271});272}273}274275private async doForwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {276if (this.pendingForwardedRequests.has(requestModel.session.sessionResource)) {277throw new Error('Request already in progress');278}279280const forwardRequest = this.doForwardRequestToChatWhenReady(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);281this.pendingForwardedRequests.set(requestModel.session.sessionResource, forwardRequest);282283try {284await forwardRequest;285} finally {286this.pendingForwardedRequests.delete(requestModel.session.sessionResource);287}288}289290private async doForwardRequestToChatWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {291const widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource);292const modeInfo = widget?.input.currentModeInfo;293294// We need a signal to know when we can resend the request to295// Chat. Waiting for the registration of the agent is not296// enough, we also need a language/tools model to be available.297298let agentActivated = false;299let agentReady = false;300let languageModelReady = false;301let toolsModelReady = false;302303const whenAgentActivated = this.whenAgentActivated(chatService).then(() => agentActivated = true);304const whenAgentReady = this.whenAgentReady(chatAgentService, modeInfo?.kind)?.then(() => agentReady = true);305if (!whenAgentReady) {306agentReady = true;307}308const whenLanguageModelReady = this.whenLanguageModelReady(languageModelsService, requestModel.modelId)?.then(() => languageModelReady = true);309if (!whenLanguageModelReady) {310languageModelReady = true;311}312const whenToolsModelReady = this.whenToolsModelReady(languageModelToolsService, requestModel)?.then(() => toolsModelReady = true);313if (!whenToolsModelReady) {314toolsModelReady = true;315}316317if (whenLanguageModelReady instanceof Promise || whenAgentReady instanceof Promise || whenToolsModelReady instanceof Promise) {318const timeoutHandle = setTimeout(() => {319progress({320kind: 'progressMessage',321content: new MarkdownString(localize('waitingChat2', "Chat is almost ready...")),322});323}, 10000);324325const disposables = new DisposableStore();326disposables.add(toDisposable(() => clearTimeout(timeoutHandle)));327try {328const ready = await Promise.race([329timeout(this.environmentService.remoteAuthority ? 60000 /* increase for remote scenarios */ : 20000).then(() => 'timedout'),330this.whenPanelAgentHasGuidance(disposables).then(() => 'panelGuidance'),331Promise.allSettled([332whenAgentActivated,333whenAgentReady,334whenLanguageModelReady,335whenToolsModelReady336])337]);338339if (ready === 'panelGuidance') {340const warningMessage = localize('chatTookLongWarningExtension', "Please try again.");341342progress({343kind: 'markdownContent',344content: new MarkdownString(warningMessage)345});346347// This means Chat is unhealthy and we cannot retry the348// request. Signal this to the outside via an event.349this._onUnresolvableError.fire();350return;351}352353if (ready === 'timedout') {354let warningMessage: string;355if (this.chatEntitlementService.anonymous) {356warningMessage = localize('chatTookLongWarningAnonymous', "Chat took too long to get ready. Please ensure that the extension `{0}` is installed and enabled. Click restart to try again if this issue persists.", defaultChat.chatExtensionId);357} else {358warningMessage = localize('chatTookLongWarning', "Chat took too long to get ready. Please ensure you are signed in to {0} and that the extension `{1}` is installed and enabled. Click restart to try again if this issue persists.", defaultChat.provider.default.name, defaultChat.chatExtensionId);359}360361// Compute language model diagnostic info362const languageModelIds = languageModelsService.getLanguageModelIds();363let languageModelDefaultCount = 0;364for (const id of languageModelIds) {365const model = languageModelsService.lookupLanguageModel(id);366if (model?.isDefaultForLocation[ChatAgentLocation.Chat]) {367languageModelDefaultCount++;368}369}370371// Compute agent diagnostic info372const defaultAgent = chatAgentService.getDefaultAgent(this.location, modeInfo?.kind);373const agentHasDefault = !!defaultAgent;374const agentDefaultIsCore = defaultAgent?.isCore ?? false;375const contributedDefaultAgent = chatAgentService.getContributedDefaultAgent(this.location);376const agentHasContributedDefault = !!contributedDefaultAgent;377const agentContributedDefaultIsCore = contributedDefaultAgent?.isCore ?? false;378const agentActivatedCount = chatAgentService.getActivatedAgents().length;379380this.logService.warn(warningMessage, {381agentActivated,382agentReady,383agentHasDefault,384agentDefaultIsCore,385agentHasContributedDefault,386agentContributedDefaultIsCore,387agentActivatedCount,388agentLocation: this.location,389agentModeKind: modeInfo?.kind,390languageModelReady,391languageModelCount: languageModelIds.length,392languageModelDefaultCount,393languageModelHasRequestedModel: !!requestModel.modelId,394toolsModelReady395});396397type ChatSetupTimeoutClassification = {398owner: 'chrmarti';399comment: 'Provides insight into chat setup timeouts.';400agentActivated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the agent was activated.' };401agentReady: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the agent was ready.' };402agentHasDefault: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether a default agent exists for the location and mode.' };403agentDefaultIsCore: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the default agent is a core agent.' };404agentHasContributedDefault: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether a contributed default agent exists for the location.' };405agentContributedDefaultIsCore: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the contributed default agent is a core agent.' };406agentActivatedCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of activated agents at timeout.' };407agentLocation: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The chat agent location.' };408agentModeKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The chat mode kind.' };409languageModelReady: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the language model was ready.' };410languageModelCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of registered language models at timeout.' };411languageModelDefaultCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of language models with isDefaultForLocation[Chat] set.' };412languageModelHasRequestedModel: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether a specific model ID was requested.' };413toolsModelReady: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the tools model was ready.' };414isRemote: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether this is a remote scenario.' };415isAnonymous: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether anonymous access is enabled.' };416matchingWelcomeViewWhen: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The when clause of the matching extension welcome view, if any.' };417};418type ChatSetupTimeoutEvent = {419agentActivated: boolean;420agentReady: boolean;421agentHasDefault: boolean;422agentDefaultIsCore: boolean;423agentHasContributedDefault: boolean;424agentContributedDefaultIsCore: boolean;425agentActivatedCount: number;426agentLocation: string;427agentModeKind: string;428languageModelReady: boolean;429languageModelCount: number;430languageModelDefaultCount: number;431languageModelHasRequestedModel: boolean;432toolsModelReady: boolean;433isRemote: boolean;434isAnonymous: boolean;435matchingWelcomeViewWhen: string;436};437const chatViewPane = this.viewsService.getActiveViewWithId(ChatViewId) as ChatViewPane | undefined;438const matchingWelcomeView = chatViewPane?.getMatchingWelcomeView();439440this.telemetryService.publicLog2<ChatSetupTimeoutEvent, ChatSetupTimeoutClassification>('chatSetup.timeout', {441agentActivated,442agentReady,443agentHasDefault,444agentDefaultIsCore,445agentHasContributedDefault,446agentContributedDefaultIsCore,447agentActivatedCount,448agentLocation: this.location,449agentModeKind: modeInfo?.kind ?? '',450languageModelReady,451languageModelCount: languageModelIds.length,452languageModelDefaultCount,453languageModelHasRequestedModel: !!requestModel.modelId,454toolsModelReady,455isRemote: !!this.environmentService.remoteAuthority,456isAnonymous: this.chatEntitlementService.anonymous,457matchingWelcomeViewWhen: matchingWelcomeView?.when.serialize() ?? (chatViewPane ? 'noWelcomeView' : 'noChatViewPane'),458});459460progress({461kind: 'warning',462content: new MarkdownString(warningMessage)463});464465progress({466kind: 'command',467command: {468id: SetupAgent.CHAT_RETRY_COMMAND_ID,469title: localize('retryChat', "Restart"),470arguments: [requestModel.session.sessionResource]471}472});473474// This means Chat is unhealthy and we cannot retry the475// request. Signal this to the outside via an event.476this._onUnresolvableError.fire();477return;478}479} finally {480disposables.dispose();481}482}483484await chatService.resendRequest(requestModel, {485...widget?.getModeRequestOptions(),486modeInfo,487userSelectedModelId: widget?.input.currentLanguageModel488});489}490491private async whenPanelAgentHasGuidance(disposables: DisposableStore): Promise<void> {492const panelAgentHasGuidance = () => chatViewsWelcomeRegistry.get().some(descriptor => this.contextKeyService.contextMatchesRules(descriptor.when));493494if (panelAgentHasGuidance()) {495return;496}497498return new Promise<void>(resolve => {499let descriptorKeys: Set<string> = new Set();500const updateDescriptorKeys = () => {501const descriptors = chatViewsWelcomeRegistry.get();502descriptorKeys = new Set(descriptors.flatMap(d => d.when.keys()));503};504updateDescriptorKeys();505506const onDidChangeRegistry = Event.map(chatViewsWelcomeRegistry.onDidChange, () => 'registry' as const);507const onDidChangeRelevantContext = Event.map(508Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(descriptorKeys)),509() => 'context' as const510);511512disposables.add(Event.any(513onDidChangeRegistry,514onDidChangeRelevantContext515)(source => {516if (source === 'registry') {517updateDescriptorKeys();518}519if (panelAgentHasGuidance()) {520resolve();521}522}));523});524}525526private whenLanguageModelReady(languageModelsService: ILanguageModelsService, modelId: string | undefined): Promise<unknown> | void {527const hasModelForRequest = () => {528if (modelId) {529return !!languageModelsService.lookupLanguageModel(modelId);530}531532for (const id of languageModelsService.getLanguageModelIds()) {533const model = languageModelsService.lookupLanguageModel(id);534if (model?.isDefaultForLocation[ChatAgentLocation.Chat]) {535return true;536}537}538539return false;540};541542if (hasModelForRequest()) {543return;544}545546return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, () => hasModelForRequest()));547}548549private whenToolsModelReady(languageModelToolsService: ILanguageModelToolsService, requestModel: IChatRequestModel): Promise<unknown> | void {550const needsToolsModel = requestModel.message.parts.some(part => part instanceof ChatRequestToolPart);551if (!needsToolsModel) {552return; // No tools in this request, no need to check553}554555// check that tools other than setup. and internal tools are registered.556for (const tool of languageModelToolsService.getAllToolsIncludingDisabled()) {557if (tool.id.startsWith('copilot_')) {558return; // we have tools!559}560}561562return Event.toPromise(Event.filter(languageModelToolsService.onDidChangeTools, () => {563for (const tool of languageModelToolsService.getAllToolsIncludingDisabled()) {564if (tool.id.startsWith('copilot_')) {565return true; // we have tools!566}567}568569return false; // no external tools found570}));571}572573private whenAgentReady(chatAgentService: IChatAgentService, mode: ChatModeKind | undefined): Promise<unknown> | void {574const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);575if (defaultAgent && !defaultAgent.isCore) {576return; // we have a default agent from an extension!577}578579return Event.toPromise(Event.filter(chatAgentService.onDidChangeAgents, () => {580const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);581return Boolean(defaultAgent && !defaultAgent.isCore);582}));583}584585private async whenAgentActivated(chatService: IChatService): Promise<void> {586try {587await chatService.activateDefaultAgent(this.location);588} catch (error) {589this.logService.error(error);590}591}592593private async doInvokeWithSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService, defaultAccountService: IDefaultAccountService): Promise<IChatAgentResult> {594this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'chat' });595596const widget = chatWidgetService.getWidgetBySessionResource(request.sessionResource);597const requestModel = widget?.viewModel?.model.getRequests().at(-1);598599const setupListener = Event.runAndSubscribe(this.controller.value.onDidChange, (() => {600switch (this.controller.value.step) {601case ChatSetupStep.SigningIn:602progress({603kind: 'progressMessage',604content: new MarkdownString(localize('setupChatSignIn2', "Signing in to {0}...", defaultAccountService.getDefaultAccountAuthenticationProvider().name)),605});606break;607case ChatSetupStep.Installing:608progress({609kind: 'progressMessage',610content: new MarkdownString(localize('installingChat', "Getting chat ready...")),611});612break;613}614}));615616let result: IChatSetupResult | undefined = undefined;617try {618result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({619disableChatViewReveal: true, // we are already in a chat context620forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithoutDialog : undefined // only enable anonymous selectively621});622} catch (error) {623this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`);624} finally {625setupListener.dispose();626}627628// User has agreed to run the setup629if (typeof result?.success === 'boolean') {630if (result.success) {631if (result.dialogSkipped) {632await widget?.clear(); // make room for the Chat welcome experience633} else if (requestModel) {634let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Chat agent...635newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Chat tools636637await this.forwardRequestToChat(newRequest, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);638}639} else {640progress({641kind: 'warning',642content: new MarkdownString(localize('chatSetupError', "Chat setup failed."))643});644}645}646647// User has cancelled the setup648else {649progress({650kind: 'markdownContent',651content: this.workspaceTrustManagementService.isWorkspaceTrusted() ? SetupAgent.SETUP_NEEDED_MESSAGE : SetupAgent.TRUST_NEEDED_MESSAGE652});653}654655return {};656}657658private replaceAgentInRequestModel(requestModel: IChatRequestModel, chatAgentService: IChatAgentService): IChatRequestModel {659const agentPart = requestModel.message.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart);660if (!agentPart) {661return requestModel;662}663664const agentId = agentPart.agent.id.replace(/setup\./, `${defaultChat.extensionId}.`.toLowerCase());665const githubAgent = chatAgentService.getAgent(agentId);666if (!githubAgent) {667return requestModel;668}669670const newAgentPart = new ChatRequestAgentPart(agentPart.range, agentPart.editorRange, githubAgent);671672return new ChatRequestModel({673session: requestModel.session as ChatModel,674message: {675parts: requestModel.message.parts.map(part => {676if (part instanceof ChatRequestAgentPart) {677return newAgentPart;678}679return part;680}),681text: requestModel.message.text682},683variableData: requestModel.variableData,684timestamp: Date.now(),685attempt: requestModel.attempt,686modeInfo: requestModel.modeInfo,687confirmation: requestModel.confirmation,688locationData: requestModel.locationData,689attachedContext: requestModel.attachedContext,690isCompleteAddedRequest: requestModel.isCompleteAddedRequest,691});692}693694private replaceToolInRequestModel(requestModel: IChatRequestModel): IChatRequestModel {695const toolPart = requestModel.message.parts.find((r): r is ChatRequestToolPart => r instanceof ChatRequestToolPart);696if (!toolPart) {697return requestModel;698}699700const toolId = toolPart.toolId.replace(/setup.tools\./, `copilot_`.toLowerCase());701const newToolPart = new ChatRequestToolPart(702toolPart.range,703toolPart.editorRange,704toolPart.toolName,705toolId,706toolPart.displayName,707toolPart.icon708);709710const chatRequestToolEntry: IChatRequestToolEntry = {711id: toolId,712name: 'new',713range: toolPart.range,714kind: 'tool',715value: undefined716};717718const variableData: IChatRequestVariableData = {719variables: [chatRequestToolEntry]720};721722return new ChatRequestModel({723session: requestModel.session as ChatModel,724message: {725parts: requestModel.message.parts.map(part => {726if (part instanceof ChatRequestToolPart) {727return newToolPart;728}729return part;730}),731text: requestModel.message.text732},733variableData: variableData,734timestamp: Date.now(),735attempt: requestModel.attempt,736modeInfo: requestModel.modeInfo,737confirmation: requestModel.confirmation,738locationData: requestModel.locationData,739attachedContext: [chatRequestToolEntry],740isCompleteAddedRequest: requestModel.isCompleteAddedRequest,741});742}743}744745export class SetupTool implements IToolImpl {746747static registerTool(instantiationService: IInstantiationService, toolData: IToolData): IDisposable {748return instantiationService.invokeFunction(accessor => {749const toolService = accessor.get(ILanguageModelToolsService);750751const tool = instantiationService.createInstance(SetupTool);752return toolService.registerTool(toolData, tool);753});754}755756async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {757const result: IToolResult = {758content: [759{760kind: 'text',761value: ''762}763]764};765766return result;767}768769async prepareToolInvocation?(parameters: unknown, token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {770return undefined;771}772}773774export class AINewSymbolNamesProvider {775776static registerProvider(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): IDisposable {777return instantiationService.invokeFunction(accessor => {778const languageFeaturesService = accessor.get(ILanguageFeaturesService);779780const provider = instantiationService.createInstance(AINewSymbolNamesProvider, context, controller);781return languageFeaturesService.newSymbolNamesProvider.register('*', provider);782});783}784785constructor(786private readonly context: ChatEntitlementContext,787private readonly controller: Lazy<ChatSetupController>,788@IInstantiationService private readonly instantiationService: IInstantiationService,789@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,790) {791}792793async provideNewSymbolNames(model: ITextModel, range: IRange, triggerKind: NewSymbolNameTriggerKind, token: CancellationToken): Promise<NewSymbolName[] | undefined> {794await this.instantiationService.invokeFunction(accessor => {795return ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({796forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined797});798});799800return [];801}802}803804export class ChatCodeActionsProvider {805806static registerProvider(instantiationService: IInstantiationService): IDisposable {807return instantiationService.invokeFunction(accessor => {808const languageFeaturesService = accessor.get(ILanguageFeaturesService);809810const provider = instantiationService.createInstance(ChatCodeActionsProvider);811return languageFeaturesService.codeActionProvider.register('*', provider);812});813}814815constructor(816@IMarkerService private readonly markerService: IMarkerService,817) {818}819820async provideCodeActions(model: ITextModel, range: Range | Selection): Promise<CodeActionList | undefined> {821const actions: CodeAction[] = [];822823// "Generate" if the line is whitespace only824// "Modify" if there is a selection825let generateOrModifyTitle: string | undefined;826let generateOrModifyCommand: Command | undefined;827if (range.isEmpty()) {828const textAtLine = model.getLineContent(range.startLineNumber);829if (/^\s*$/.test(textAtLine)) {830generateOrModifyTitle = localize('generate', "Generate");831generateOrModifyCommand = AICodeActionsHelper.generate(range);832}833} else {834const textInSelection = model.getValueInRange(range);835if (!/^\s*$/.test(textInSelection)) {836generateOrModifyTitle = localize('modify', "Modify");837generateOrModifyCommand = AICodeActionsHelper.modify(range);838}839}840841if (generateOrModifyTitle && generateOrModifyCommand) {842actions.push({843kind: CodeActionKind.RefactorRewrite.append('copilot').value,844isAI: true,845title: generateOrModifyTitle,846command: generateOrModifyCommand,847});848}849850const markers = AICodeActionsHelper.warningOrErrorMarkersAtRange(this.markerService, model.uri, range);851if (markers.length > 0) {852853// "Fix" if there are diagnostics in the range854actions.push({855kind: CodeActionKind.QuickFix.append('copilot').value,856isAI: true,857diagnostics: markers,858title: localize('fix', "Fix"),859command: AICodeActionsHelper.fixMarkers(markers, range)860});861862// "Explain" if there are diagnostics in the range863actions.push({864kind: CodeActionKind.QuickFix.append('explain').append('copilot').value,865isAI: true,866diagnostics: markers,867title: localize('explain', "Explain"),868command: AICodeActionsHelper.explainMarkers(markers)869});870}871872return {873actions,874dispose() { }875};876}877}878879export class AICodeActionsHelper {880881static warningOrErrorMarkersAtRange(markerService: IMarkerService, resource: URI, range: Range | Selection): IMarker[] {882return markerService883.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning })884.filter(marker => range.startLineNumber <= marker.endLineNumber && range.endLineNumber >= marker.startLineNumber);885}886887static modify(range: Range): Command {888return {889id: INLINE_CHAT_START,890title: localize('modify', "Modify"),891arguments: [892{893initialSelection: this.rangeToSelection(range),894initialRange: range,895position: range.getStartPosition()896} satisfies { initialSelection: ISelection; initialRange: IRange; position: IPosition }897]898};899}900901static generate(range: Range): Command {902return {903id: INLINE_CHAT_START,904title: localize('generate', "Generate"),905arguments: [906{907initialSelection: this.rangeToSelection(range),908initialRange: range,909position: range.getStartPosition()910} satisfies { initialSelection: ISelection; initialRange: IRange; position: IPosition }911]912};913}914915private static rangeToSelection(range: Range): ISelection {916return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);917}918919static explainMarkers(markers: IMarker[]): Command {920return {921id: CHAT_OPEN_ACTION_ID,922title: localize('explain', "Explain"),923arguments: [924{925query: `@workspace /explain ${markers.map(marker => marker.message).join(', ')}`,926isPartialQuery: true927} satisfies { query: string; isPartialQuery: boolean }928]929};930}931932static fixMarkers(markers: IMarker[], range: Range): Command {933return {934id: INLINE_CHAT_START,935title: localize('fix', "Fix"),936arguments: [937{938message: `/fix ${markers.map(marker => marker.message).join(', ')}`,939initialSelection: this.rangeToSelection(range),940initialRange: range,941position: range.getStartPosition()942} satisfies { message: string; initialSelection: ISelection; initialRange: IRange; position: IPosition }943]944};945}946}947948949