Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js';6import { 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 } from '../../../../../base/common/lifecycle.js';14import { URI } from '../../../../../base/common/uri.js';15import { localize, localize2 } from '../../../../../nls.js';16import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';17import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';18import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';19import { ILogService } from '../../../../../platform/log/common/log.js';20import product from '../../../../../platform/product/common/product.js';21import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';22import { IWorkspaceTrustManagementService } from '../../../../../platform/workspace/common/workspaceTrust.js';23import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js';24import { nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';25import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolDataSource, ToolProgress } from '../../common/tools/languageModelToolsService.js';26import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../../common/participants/chatAgents.js';27import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';28import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../../common/model/chatModel.js';29import { ChatMode } from '../../common/chatModes.js';30import { ChatRequestAgentPart, ChatRequestToolPart } from '../../common/requestParser/chatParserTypes.js';31import { IChatProgress, IChatService } from '../../common/chatService/chatService.js';32import { IChatRequestToolEntry } from '../../common/attachments/chatVariableEntries.js';33import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';34import { ILanguageModelsService } from '../../common/languageModels.js';35import { CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from '../actions/chatActions.js';36import { IChatWidgetService } from '../chat.js';37import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';38import { CodeAction, CodeActionList, Command, NewSymbolName, NewSymbolNameTriggerKind } from '../../../../../editor/common/languages.js';39import { ITextModel } from '../../../../../editor/common/model.js';40import { IRange, Range } from '../../../../../editor/common/core/range.js';41import { ISelection, Selection } from '../../../../../editor/common/core/selection.js';42import { ResourceMap } from '../../../../../base/common/map.js';43import { CodeActionKind } from '../../../../../editor/contrib/codeAction/common/types.js';44import { ACTION_START as INLINE_CHAT_START } from '../../../inlineChat/common/inlineChat.js';45import { IPosition } from '../../../../../editor/common/core/position.js';46import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';47import { ChatSetupController } from './chatSetupController.js';48import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult } from './chatSetup.js';49import { ChatSetup } from './chatSetupRunner.js';5051const defaultChat = {52extensionId: product.defaultChatAgent?.extensionId ?? '',53chatExtensionId: product.defaultChatAgent?.chatExtensionId ?? '',54provider: product.defaultChatAgent?.provider ?? { default: { id: '', name: '' }, enterprise: { id: '', name: '' }, apple: { id: '', name: '' }, google: { id: '', name: '' } },55};5657const ToolsAgentContextKey = ContextKeyExpr.and(58ContextKeyExpr.equals(`config.${ChatConfiguration.AgentEnabled}`, true),59ContextKeyExpr.not(`previewFeaturesDisabled`) // Set by extension60);6162export class SetupAgent extends Disposable implements IChatAgentImplementation {6364static registerDefaultAgents(instantiationService: IInstantiationService, location: ChatAgentLocation, mode: ChatModeKind | undefined, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {65return instantiationService.invokeFunction(accessor => {66const chatAgentService = accessor.get(IChatAgentService);6768let id: string;69let description = ChatMode.Ask.description.get();70switch (location) {71case ChatAgentLocation.Chat:72if (mode === ChatModeKind.Ask) {73id = 'setup.chat';74} else if (mode === ChatModeKind.Edit) {75id = 'setup.edits';76description = ChatMode.Edit.description.get();77} else {78id = 'setup.agent';79description = ChatMode.Agent.description.get();80}81break;82case ChatAgentLocation.Terminal:83id = 'setup.terminal';84break;85case ChatAgentLocation.EditorInline:86id = 'setup.editor';87break;88case ChatAgentLocation.Notebook:89id = 'setup.notebook';90break;91}9293return 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);94});95}9697static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): IDisposable {98return instantiationService.invokeFunction(accessor => {99const chatAgentService = accessor.get(IChatAgentService);100101const disposables = new DisposableStore();102103// Register VSCode agent104const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Chat, undefined, context, controller);105disposables.add(vscodeDisposable);106107// Register workspace agent108const { disposable: workspaceDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.workspace', 'workspace', false, localize2('workspaceAgentDescription', "Ask about your workspace").value, ChatAgentLocation.Chat, undefined, context, controller);109disposables.add(workspaceDisposable);110111// Register terminal agent112const { disposable: terminalDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.terminal.agent', 'terminal', false, localize2('terminalAgentDescription', "Ask how to do something in the terminal").value, ChatAgentLocation.Chat, undefined, context, controller);113disposables.add(terminalDisposable);114115// Register tools116disposables.add(SetupTool.registerTool(instantiationService, {117id: 'setup_tools_createNewWorkspace',118source: ToolDataSource.Internal,119icon: Codicon.newFolder,120displayName: localize('setupToolDisplayName', "New Workspace"),121modelDescription: 'Scaffold a new workspace in VS Code',122userDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"),123canBeReferencedInPrompt: true,124toolReferenceName: 'new',125when: ContextKeyExpr.true(),126}));127128return disposables;129});130}131132private static doRegisterAgent(instantiationService: IInstantiationService, chatAgentService: IChatAgentService, id: string, name: string, isDefault: boolean, description: string, location: ChatAgentLocation, mode: ChatModeKind | undefined, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {133const disposables = new DisposableStore();134disposables.add(chatAgentService.registerAgent(id, {135id,136name,137isDefault,138isCore: true,139modes: mode ? [mode] : [ChatModeKind.Ask],140when: mode === ChatModeKind.Agent ? ToolsAgentContextKey?.serialize() : undefined,141slashCommands: [],142disambiguation: [],143locations: [location],144metadata: { helpTextPrefix: SetupAgent.SETUP_NEEDED_MESSAGE },145description,146extensionId: nullExtensionDescription.identifier,147extensionVersion: undefined,148extensionDisplayName: nullExtensionDescription.name,149extensionPublisherId: nullExtensionDescription.publisher150}));151152const agent = disposables.add(instantiationService.createInstance(SetupAgent, context, controller, location));153disposables.add(chatAgentService.registerAgentImplementation(id, agent));154if (mode === ChatModeKind.Agent) {155chatAgentService.updateAgent(id, { themeIcon: Codicon.tools });156}157158return { agent, disposable: disposables };159}160161private static readonly SETUP_NEEDED_MESSAGE = new MarkdownString(localize('settingUpCopilotNeeded', "You need to set up GitHub Copilot and be signed in to use Chat."));162private static readonly TRUST_NEEDED_MESSAGE = new MarkdownString(localize('trustNeeded', "You need to trust this workspace to use Chat."));163164private readonly _onUnresolvableError = this._register(new Emitter<void>());165readonly onUnresolvableError = this._onUnresolvableError.event;166167private readonly pendingForwardedRequests = new ResourceMap<Promise<void>>();168169constructor(170private readonly context: ChatEntitlementContext,171private readonly controller: Lazy<ChatSetupController>,172private readonly location: ChatAgentLocation,173@IInstantiationService private readonly instantiationService: IInstantiationService,174@ILogService private readonly logService: ILogService,175@IConfigurationService private readonly configurationService: IConfigurationService,176@ITelemetryService private readonly telemetryService: ITelemetryService,177@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,178@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,179@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,180) {181super();182}183184async invoke(request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void): Promise<IChatAgentResult> {185return this.instantiationService.invokeFunction(async accessor /* using accessor for lazy loading */ => {186const chatService = accessor.get(IChatService);187const languageModelsService = accessor.get(ILanguageModelsService);188const chatWidgetService = accessor.get(IChatWidgetService);189const chatAgentService = accessor.get(IChatAgentService);190const languageModelToolsService = accessor.get(ILanguageModelToolsService);191192return this.doInvoke(request, part => progress([part]), chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);193});194}195196private async doInvoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {197if (198!this.context.state.installed || // Extension not installed: run setup to install199this.context.state.disabled || // Extension disabled: run setup to enable200this.context.state.untrusted || // Workspace untrusted: run setup to ask for trust201this.context.state.entitlement === ChatEntitlement.Available || // Entitlement available: run setup to sign up202(203this.context.state.entitlement === ChatEntitlement.Unknown && // Entitlement unknown: run setup to sign in / sign up204!this.chatEntitlementService.anonymous // unless anonymous access is enabled205)206) {207return this.doInvokeWithSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);208}209210return this.doInvokeWithoutSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);211}212213private async doInvokeWithoutSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {214const requestModel = chatWidgetService.getWidgetBySessionResource(request.sessionResource)?.viewModel?.model.getRequests().at(-1);215if (!requestModel) {216this.logService.error('[chat setup] Request model not found, cannot redispatch request.');217return {}; // this should not happen218}219220progress({221kind: 'progressMessage',222content: new MarkdownString(localize('waitingChat', "Getting chat ready...")),223});224225await this.forwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);226227return {};228}229230private async forwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {231try {232await this.doForwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);233} catch (error) {234this.logService.error('[chat setup] Failed to forward request to chat', error);235236progress({237kind: 'warning',238content: new MarkdownString(localize('copilotUnavailableWarning', "Failed to get a response. Please try again."))239});240}241}242243private async doForwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {244if (this.pendingForwardedRequests.has(requestModel.session.sessionResource)) {245throw new Error('Request already in progress');246}247248const forwardRequest = this.doForwardRequestToChatWhenReady(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);249this.pendingForwardedRequests.set(requestModel.session.sessionResource, forwardRequest);250251try {252await forwardRequest;253} finally {254this.pendingForwardedRequests.delete(requestModel.session.sessionResource);255}256}257258private async doForwardRequestToChatWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {259const widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource);260const modeInfo = widget?.input.currentModeInfo;261262// We need a signal to know when we can resend the request to263// Chat. Waiting for the registration of the agent is not264// enough, we also need a language/tools model to be available.265266let agentActivated = false;267let agentReady = false;268let languageModelReady = false;269let toolsModelReady = false;270271const whenAgentActivated = this.whenAgentActivated(chatService).then(() => agentActivated = true);272const whenAgentReady = this.whenAgentReady(chatAgentService, modeInfo?.kind)?.then(() => agentReady = true);273const whenLanguageModelReady = this.whenLanguageModelReady(languageModelsService, requestModel.modelId)?.then(() => languageModelReady = true);274const whenToolsModelReady = this.whenToolsModelReady(languageModelToolsService, requestModel)?.then(() => toolsModelReady = true);275276if (whenLanguageModelReady instanceof Promise || whenAgentReady instanceof Promise || whenToolsModelReady instanceof Promise) {277const timeoutHandle = setTimeout(() => {278progress({279kind: 'progressMessage',280content: new MarkdownString(localize('waitingChat2', "Chat is almost ready...")),281});282}, 10000);283284try {285const ready = await Promise.race([286timeout(this.environmentService.remoteAuthority ? 60000 /* increase for remote scenarios */ : 20000).then(() => 'timedout'),287Promise.allSettled([288whenAgentActivated,289whenAgentReady,290whenLanguageModelReady,291whenToolsModelReady292])293]);294295if (ready === 'timedout') {296let warningMessage: string;297if (this.chatEntitlementService.anonymous) {298warningMessage = localize('chatTookLongWarningAnonymous', "Chat took too long to get ready. Please ensure that the extension `{0}` is installed and enabled.", defaultChat.chatExtensionId);299} else {300warningMessage = 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.", defaultChat.provider.default.name, defaultChat.chatExtensionId);301}302303this.logService.warn(warningMessage, {304agentActivated,305agentReady,306languageModelReady,307toolsModelReady308});309310progress({311kind: 'warning',312content: new MarkdownString(warningMessage)313});314315// This means Chat is unhealthy and we cannot retry the316// request. Signal this to the outside via an event.317this._onUnresolvableError.fire();318return;319}320} finally {321clearTimeout(timeoutHandle);322}323}324325await chatService.resendRequest(requestModel, {326...widget?.getModeRequestOptions(),327modeInfo,328userSelectedModelId: widget?.input.currentLanguageModel329});330}331332private whenLanguageModelReady(languageModelsService: ILanguageModelsService, modelId: string | undefined): Promise<unknown> | void {333const hasModelForRequest = () => {334if (modelId) {335return !!languageModelsService.lookupLanguageModel(modelId);336}337338for (const id of languageModelsService.getLanguageModelIds()) {339const model = languageModelsService.lookupLanguageModel(id);340if (model?.isDefault) {341return true;342}343}344345return false;346};347348if (hasModelForRequest()) {349return;350}351352return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, () => hasModelForRequest()));353}354355private whenToolsModelReady(languageModelToolsService: ILanguageModelToolsService, requestModel: IChatRequestModel): Promise<unknown> | void {356const needsToolsModel = requestModel.message.parts.some(part => part instanceof ChatRequestToolPart);357if (!needsToolsModel) {358return; // No tools in this request, no need to check359}360361// check that tools other than setup. and internal tools are registered.362for (const tool of languageModelToolsService.getTools()) {363if (tool.id.startsWith('copilot_')) {364return; // we have tools!365}366}367368return Event.toPromise(Event.filter(languageModelToolsService.onDidChangeTools, () => {369for (const tool of languageModelToolsService.getTools()) {370if (tool.id.startsWith('copilot_')) {371return true; // we have tools!372}373}374375return false; // no external tools found376}));377}378379private whenAgentReady(chatAgentService: IChatAgentService, mode: ChatModeKind | undefined): Promise<unknown> | void {380const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);381if (defaultAgent && !defaultAgent.isCore) {382return; // we have a default agent from an extension!383}384385return Event.toPromise(Event.filter(chatAgentService.onDidChangeAgents, () => {386const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);387return Boolean(defaultAgent && !defaultAgent.isCore);388}));389}390391private async whenAgentActivated(chatService: IChatService): Promise<void> {392try {393await chatService.activateDefaultAgent(this.location);394} catch (error) {395this.logService.error(error);396}397}398399private async doInvokeWithSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {400this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'chat' });401402const widget = chatWidgetService.getWidgetBySessionResource(request.sessionResource);403const requestModel = widget?.viewModel?.model.getRequests().at(-1);404405const setupListener = Event.runAndSubscribe(this.controller.value.onDidChange, (() => {406switch (this.controller.value.step) {407case ChatSetupStep.SigningIn:408progress({409kind: 'progressMessage',410content: new MarkdownString(localize('setupChatSignIn2', "Signing in to {0}...", ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.provider.enterprise.id ? defaultChat.provider.enterprise.name : defaultChat.provider.default.name)),411});412break;413case ChatSetupStep.Installing:414progress({415kind: 'progressMessage',416content: new MarkdownString(localize('installingChat', "Getting chat ready...")),417});418break;419}420}));421422let result: IChatSetupResult | undefined = undefined;423try {424result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({425disableChatViewReveal: true, // we are already in a chat context426forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithoutDialog : undefined // only enable anonymous selectively427});428} catch (error) {429this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`);430} finally {431setupListener.dispose();432}433434// User has agreed to run the setup435if (typeof result?.success === 'boolean') {436if (result.success) {437if (result.dialogSkipped) {438await widget?.clear(); // make room for the Chat welcome experience439} else if (requestModel) {440let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Chat agent...441newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Chat tools442443await this.forwardRequestToChat(newRequest, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);444}445} else {446progress({447kind: 'warning',448content: new MarkdownString(localize('chatSetupError', "Chat setup failed."))449});450}451}452453// User has cancelled the setup454else {455progress({456kind: 'markdownContent',457content: this.workspaceTrustManagementService.isWorkspaceTrusted() ? SetupAgent.SETUP_NEEDED_MESSAGE : SetupAgent.TRUST_NEEDED_MESSAGE458});459}460461return {};462}463464private replaceAgentInRequestModel(requestModel: IChatRequestModel, chatAgentService: IChatAgentService): IChatRequestModel {465const agentPart = requestModel.message.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart);466if (!agentPart) {467return requestModel;468}469470const agentId = agentPart.agent.id.replace(/setup\./, `${defaultChat.extensionId}.`.toLowerCase());471const githubAgent = chatAgentService.getAgent(agentId);472if (!githubAgent) {473return requestModel;474}475476const newAgentPart = new ChatRequestAgentPart(agentPart.range, agentPart.editorRange, githubAgent);477478return new ChatRequestModel({479session: requestModel.session as ChatModel,480message: {481parts: requestModel.message.parts.map(part => {482if (part instanceof ChatRequestAgentPart) {483return newAgentPart;484}485return part;486}),487text: requestModel.message.text488},489variableData: requestModel.variableData,490timestamp: Date.now(),491attempt: requestModel.attempt,492modeInfo: requestModel.modeInfo,493confirmation: requestModel.confirmation,494locationData: requestModel.locationData,495attachedContext: requestModel.attachedContext,496isCompleteAddedRequest: requestModel.isCompleteAddedRequest,497});498}499500private replaceToolInRequestModel(requestModel: IChatRequestModel): IChatRequestModel {501const toolPart = requestModel.message.parts.find((r): r is ChatRequestToolPart => r instanceof ChatRequestToolPart);502if (!toolPart) {503return requestModel;504}505506const toolId = toolPart.toolId.replace(/setup.tools\./, `copilot_`.toLowerCase());507const newToolPart = new ChatRequestToolPart(508toolPart.range,509toolPart.editorRange,510toolPart.toolName,511toolId,512toolPart.displayName,513toolPart.icon514);515516const chatRequestToolEntry: IChatRequestToolEntry = {517id: toolId,518name: 'new',519range: toolPart.range,520kind: 'tool',521value: undefined522};523524const variableData: IChatRequestVariableData = {525variables: [chatRequestToolEntry]526};527528return new ChatRequestModel({529session: requestModel.session as ChatModel,530message: {531parts: requestModel.message.parts.map(part => {532if (part instanceof ChatRequestToolPart) {533return newToolPart;534}535return part;536}),537text: requestModel.message.text538},539variableData: variableData,540timestamp: Date.now(),541attempt: requestModel.attempt,542modeInfo: requestModel.modeInfo,543confirmation: requestModel.confirmation,544locationData: requestModel.locationData,545attachedContext: [chatRequestToolEntry],546isCompleteAddedRequest: requestModel.isCompleteAddedRequest,547});548}549}550551export class SetupTool implements IToolImpl {552553static registerTool(instantiationService: IInstantiationService, toolData: IToolData): IDisposable {554return instantiationService.invokeFunction(accessor => {555const toolService = accessor.get(ILanguageModelToolsService);556557const tool = instantiationService.createInstance(SetupTool);558return toolService.registerTool(toolData, tool);559});560}561562async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {563const result: IToolResult = {564content: [565{566kind: 'text',567value: ''568}569]570};571572return result;573}574575async prepareToolInvocation?(parameters: unknown, token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {576return undefined;577}578}579580export class AINewSymbolNamesProvider {581582static registerProvider(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): IDisposable {583return instantiationService.invokeFunction(accessor => {584const languageFeaturesService = accessor.get(ILanguageFeaturesService);585586const provider = instantiationService.createInstance(AINewSymbolNamesProvider, context, controller);587return languageFeaturesService.newSymbolNamesProvider.register('*', provider);588});589}590591constructor(592private readonly context: ChatEntitlementContext,593private readonly controller: Lazy<ChatSetupController>,594@IInstantiationService private readonly instantiationService: IInstantiationService,595@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,596) {597}598599async provideNewSymbolNames(model: ITextModel, range: IRange, triggerKind: NewSymbolNameTriggerKind, token: CancellationToken): Promise<NewSymbolName[] | undefined> {600await this.instantiationService.invokeFunction(accessor => {601return ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({602forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined603});604});605606return [];607}608}609610export class ChatCodeActionsProvider {611612static registerProvider(instantiationService: IInstantiationService): IDisposable {613return instantiationService.invokeFunction(accessor => {614const languageFeaturesService = accessor.get(ILanguageFeaturesService);615616const provider = instantiationService.createInstance(ChatCodeActionsProvider);617return languageFeaturesService.codeActionProvider.register('*', provider);618});619}620621constructor(622@IMarkerService private readonly markerService: IMarkerService,623) {624}625626async provideCodeActions(model: ITextModel, range: Range | Selection): Promise<CodeActionList | undefined> {627const actions: CodeAction[] = [];628629// "Generate" if the line is whitespace only630// "Modify" if there is a selection631let generateOrModifyTitle: string | undefined;632let generateOrModifyCommand: Command | undefined;633if (range.isEmpty()) {634const textAtLine = model.getLineContent(range.startLineNumber);635if (/^\s*$/.test(textAtLine)) {636generateOrModifyTitle = localize('generate', "Generate");637generateOrModifyCommand = AICodeActionsHelper.generate(range);638}639} else {640const textInSelection = model.getValueInRange(range);641if (!/^\s*$/.test(textInSelection)) {642generateOrModifyTitle = localize('modify', "Modify");643generateOrModifyCommand = AICodeActionsHelper.modify(range);644}645}646647if (generateOrModifyTitle && generateOrModifyCommand) {648actions.push({649kind: CodeActionKind.RefactorRewrite.append('copilot').value,650isAI: true,651title: generateOrModifyTitle,652command: generateOrModifyCommand,653});654}655656const markers = AICodeActionsHelper.warningOrErrorMarkersAtRange(this.markerService, model.uri, range);657if (markers.length > 0) {658659// "Fix" if there are diagnostics in the range660actions.push({661kind: CodeActionKind.QuickFix.append('copilot').value,662isAI: true,663diagnostics: markers,664title: localize('fix', "Fix"),665command: AICodeActionsHelper.fixMarkers(markers, range)666});667668// "Explain" if there are diagnostics in the range669actions.push({670kind: CodeActionKind.QuickFix.append('explain').append('copilot').value,671isAI: true,672diagnostics: markers,673title: localize('explain', "Explain"),674command: AICodeActionsHelper.explainMarkers(markers)675});676}677678return {679actions,680dispose() { }681};682}683}684685export class AICodeActionsHelper {686687static warningOrErrorMarkersAtRange(markerService: IMarkerService, resource: URI, range: Range | Selection): IMarker[] {688return markerService689.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning })690.filter(marker => range.startLineNumber <= marker.endLineNumber && range.endLineNumber >= marker.startLineNumber);691}692693static modify(range: Range): Command {694return {695id: INLINE_CHAT_START,696title: localize('modify', "Modify"),697arguments: [698{699initialSelection: this.rangeToSelection(range),700initialRange: range,701position: range.getStartPosition()702} satisfies { initialSelection: ISelection; initialRange: IRange; position: IPosition }703]704};705}706707static generate(range: Range): Command {708return {709id: INLINE_CHAT_START,710title: localize('generate', "Generate"),711arguments: [712{713initialSelection: this.rangeToSelection(range),714initialRange: range,715position: range.getStartPosition()716} satisfies { initialSelection: ISelection; initialRange: IRange; position: IPosition }717]718};719}720721private static rangeToSelection(range: Range): ISelection {722return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);723}724725static explainMarkers(markers: IMarker[]): Command {726return {727id: CHAT_OPEN_ACTION_ID,728title: localize('explain', "Explain"),729arguments: [730{731query: `@workspace /explain ${markers.map(marker => marker.message).join(', ')}`,732isPartialQuery: true733} satisfies { query: string; isPartialQuery: boolean }734]735};736}737738static fixMarkers(markers: IMarker[], range: Range): Command {739return {740id: INLINE_CHAT_START,741title: localize('fix', "Fix"),742arguments: [743{744message: `/fix ${markers.map(marker => marker.message).join(', ')}`,745initialSelection: this.rangeToSelection(range),746initialRange: range,747position: range.getStartPosition()748} satisfies { message: string; initialSelection: ISelection; initialRange: IRange; position: IPosition }749]750};751}752}753754755