Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupProviders.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from '../../../../../base/common/actions.js';
7
import { timeout } from '../../../../../base/common/async.js';
8
import { CancellationToken } from '../../../../../base/common/cancellation.js';
9
import { Codicon } from '../../../../../base/common/codicons.js';
10
import { toErrorMessage } from '../../../../../base/common/errorMessage.js';
11
import { Emitter, Event } from '../../../../../base/common/event.js';
12
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
13
import { Lazy } from '../../../../../base/common/lazy.js';
14
import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
15
import { URI } from '../../../../../base/common/uri.js';
16
import { localize, localize2 } from '../../../../../nls.js';
17
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
18
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
19
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
20
import { ILogService } from '../../../../../platform/log/common/log.js';
21
import product from '../../../../../platform/product/common/product.js';
22
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
23
import { IWorkspaceTrustManagementService } from '../../../../../platform/workspace/common/workspaceTrust.js';
24
import { IWorkbenchEnvironmentService } from '../../../../services/environment/common/environmentService.js';
25
import { nullExtensionDescription } from '../../../../services/extensions/common/extensions.js';
26
import { CountTokensCallback, ILanguageModelToolsService, IPreparedToolInvocation, IToolData, IToolImpl, IToolInvocation, IToolResult, ToolDataSource, ToolProgress } from '../../common/tools/languageModelToolsService.js';
27
import { IChatAgentImplementation, IChatAgentRequest, IChatAgentResult, IChatAgentService } from '../../common/participants/chatAgents.js';
28
import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';
29
import { ChatModel, ChatRequestModel, IChatRequestModel, IChatRequestVariableData } from '../../common/model/chatModel.js';
30
import { ChatMode } from '../../common/chatModes.js';
31
import { ChatRequestAgentPart, ChatRequestToolPart } from '../../common/requestParser/chatParserTypes.js';
32
import { IChatProgress, IChatService } from '../../common/chatService/chatService.js';
33
import { IChatRequestToolEntry } from '../../common/attachments/chatVariableEntries.js';
34
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';
35
import { ILanguageModelsService } from '../../common/languageModels.js';
36
import { CHAT_OPEN_ACTION_ID, CHAT_SETUP_ACTION_ID } from '../actions/chatActions.js';
37
import { IChatWidgetService } from '../chat.js';
38
import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';
39
import { CodeAction, CodeActionList, Command, NewSymbolName, NewSymbolNameTriggerKind } from '../../../../../editor/common/languages.js';
40
import { ITextModel } from '../../../../../editor/common/model.js';
41
import { IRange, Range } from '../../../../../editor/common/core/range.js';
42
import { ISelection, Selection } from '../../../../../editor/common/core/selection.js';
43
import { ResourceMap } from '../../../../../base/common/map.js';
44
import { CodeActionKind } from '../../../../../editor/contrib/codeAction/common/types.js';
45
import { ACTION_START as INLINE_CHAT_START } from '../../../inlineChat/common/inlineChat.js';
46
import { IPosition } from '../../../../../editor/common/core/position.js';
47
import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';
48
import { ChatSetupController } from './chatSetupController.js';
49
import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult } from './chatSetup.js';
50
import { ChatSetup } from './chatSetupRunner.js';
51
52
const defaultChat = {
53
extensionId: product.defaultChatAgent?.extensionId ?? '',
54
chatExtensionId: product.defaultChatAgent?.chatExtensionId ?? '',
55
provider: product.defaultChatAgent?.provider ?? { default: { id: '', name: '' }, enterprise: { id: '', name: '' }, apple: { id: '', name: '' }, google: { id: '', name: '' } },
56
};
57
58
const ToolsAgentContextKey = ContextKeyExpr.and(
59
ContextKeyExpr.equals(`config.${ChatConfiguration.AgentEnabled}`, true),
60
ContextKeyExpr.not(`previewFeaturesDisabled`) // Set by extension
61
);
62
63
export class SetupAgent extends Disposable implements IChatAgentImplementation {
64
65
static registerDefaultAgents(instantiationService: IInstantiationService, location: ChatAgentLocation, mode: ChatModeKind | undefined, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): { agent: SetupAgent; disposable: IDisposable } {
66
return instantiationService.invokeFunction(accessor => {
67
const chatAgentService = accessor.get(IChatAgentService);
68
69
let id: string;
70
let description = ChatMode.Ask.description.get();
71
switch (location) {
72
case ChatAgentLocation.Chat:
73
if (mode === ChatModeKind.Ask) {
74
id = 'setup.chat';
75
} else if (mode === ChatModeKind.Edit) {
76
id = 'setup.edits';
77
description = ChatMode.Edit.description.get();
78
} else {
79
id = 'setup.agent';
80
description = ChatMode.Agent.description.get();
81
}
82
break;
83
case ChatAgentLocation.Terminal:
84
id = 'setup.terminal';
85
break;
86
case ChatAgentLocation.EditorInline:
87
id = 'setup.editor';
88
break;
89
case ChatAgentLocation.Notebook:
90
id = 'setup.notebook';
91
break;
92
}
93
94
return 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);
95
});
96
}
97
98
static registerBuiltInAgents(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): IDisposable {
99
return instantiationService.invokeFunction(accessor => {
100
const chatAgentService = accessor.get(IChatAgentService);
101
102
const disposables = new DisposableStore();
103
104
// Register VSCode agent
105
const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Chat, undefined, context, controller);
106
disposables.add(vscodeDisposable);
107
108
// Register workspace agent
109
const { disposable: workspaceDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.workspace', 'workspace', false, localize2('workspaceAgentDescription', "Ask about your workspace").value, ChatAgentLocation.Chat, undefined, context, controller);
110
disposables.add(workspaceDisposable);
111
112
// Register terminal agent
113
const { 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);
114
disposables.add(terminalDisposable);
115
116
// Register tools
117
disposables.add(SetupTool.registerTool(instantiationService, {
118
id: 'setup_tools_createNewWorkspace',
119
source: ToolDataSource.Internal,
120
icon: Codicon.newFolder,
121
displayName: localize('setupToolDisplayName', "New Workspace"),
122
modelDescription: 'Scaffold a new workspace in VS Code',
123
userDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"),
124
canBeReferencedInPrompt: true,
125
toolReferenceName: 'new',
126
when: ContextKeyExpr.true(),
127
}));
128
129
return disposables;
130
});
131
}
132
133
private 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 } {
134
const disposables = new DisposableStore();
135
disposables.add(chatAgentService.registerAgent(id, {
136
id,
137
name,
138
isDefault,
139
isCore: true,
140
modes: mode ? [mode] : [ChatModeKind.Ask],
141
when: mode === ChatModeKind.Agent ? ToolsAgentContextKey?.serialize() : undefined,
142
slashCommands: [],
143
disambiguation: [],
144
locations: [location],
145
metadata: { helpTextPrefix: SetupAgent.SETUP_NEEDED_MESSAGE },
146
description,
147
extensionId: nullExtensionDescription.identifier,
148
extensionVersion: undefined,
149
extensionDisplayName: nullExtensionDescription.name,
150
extensionPublisherId: nullExtensionDescription.publisher
151
}));
152
153
const agent = disposables.add(instantiationService.createInstance(SetupAgent, context, controller, location));
154
disposables.add(chatAgentService.registerAgentImplementation(id, agent));
155
if (mode === ChatModeKind.Agent) {
156
chatAgentService.updateAgent(id, { themeIcon: Codicon.tools });
157
}
158
159
return { agent, disposable: disposables };
160
}
161
162
private static readonly SETUP_NEEDED_MESSAGE = new MarkdownString(localize('settingUpCopilotNeeded', "You need to set up GitHub Copilot and be signed in to use Chat."));
163
private static readonly TRUST_NEEDED_MESSAGE = new MarkdownString(localize('trustNeeded', "You need to trust this workspace to use Chat."));
164
165
private readonly _onUnresolvableError = this._register(new Emitter<void>());
166
readonly onUnresolvableError = this._onUnresolvableError.event;
167
168
private readonly pendingForwardedRequests = new ResourceMap<Promise<void>>();
169
170
constructor(
171
private readonly context: ChatEntitlementContext,
172
private readonly controller: Lazy<ChatSetupController>,
173
private readonly location: ChatAgentLocation,
174
@IInstantiationService private readonly instantiationService: IInstantiationService,
175
@ILogService private readonly logService: ILogService,
176
@IConfigurationService private readonly configurationService: IConfigurationService,
177
@ITelemetryService private readonly telemetryService: ITelemetryService,
178
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
179
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
180
@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,
181
) {
182
super();
183
}
184
185
async invoke(request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void): Promise<IChatAgentResult> {
186
return this.instantiationService.invokeFunction(async accessor /* using accessor for lazy loading */ => {
187
const chatService = accessor.get(IChatService);
188
const languageModelsService = accessor.get(ILanguageModelsService);
189
const chatWidgetService = accessor.get(IChatWidgetService);
190
const chatAgentService = accessor.get(IChatAgentService);
191
const languageModelToolsService = accessor.get(ILanguageModelToolsService);
192
193
return this.doInvoke(request, part => progress([part]), chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);
194
});
195
}
196
197
private async doInvoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {
198
if (
199
!this.context.state.installed || // Extension not installed: run setup to install
200
this.context.state.disabled || // Extension disabled: run setup to enable
201
this.context.state.untrusted || // Workspace untrusted: run setup to ask for trust
202
this.context.state.entitlement === ChatEntitlement.Available || // Entitlement available: run setup to sign up
203
(
204
this.context.state.entitlement === ChatEntitlement.Unknown && // Entitlement unknown: run setup to sign in / sign up
205
!this.chatEntitlementService.anonymous // unless anonymous access is enabled
206
)
207
) {
208
return this.doInvokeWithSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);
209
}
210
211
return this.doInvokeWithoutSetup(request, progress, chatService, languageModelsService, chatWidgetService, chatAgentService, languageModelToolsService);
212
}
213
214
private async doInvokeWithoutSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {
215
const requestModel = chatWidgetService.getWidgetBySessionResource(request.sessionResource)?.viewModel?.model.getRequests().at(-1);
216
if (!requestModel) {
217
this.logService.error('[chat setup] Request model not found, cannot redispatch request.');
218
return {}; // this should not happen
219
}
220
221
progress({
222
kind: 'progressMessage',
223
content: new MarkdownString(localize('waitingChat', "Getting chat ready...")),
224
});
225
226
await this.forwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);
227
228
return {};
229
}
230
231
private async forwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {
232
try {
233
await this.doForwardRequestToChat(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);
234
} catch (error) {
235
this.logService.error('[chat setup] Failed to forward request to chat', error);
236
237
progress({
238
kind: 'warning',
239
content: new MarkdownString(localize('copilotUnavailableWarning', "Failed to get a response. Please try again."))
240
});
241
}
242
}
243
244
private async doForwardRequestToChat(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {
245
if (this.pendingForwardedRequests.has(requestModel.session.sessionResource)) {
246
throw new Error('Request already in progress');
247
}
248
249
const forwardRequest = this.doForwardRequestToChatWhenReady(requestModel, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);
250
this.pendingForwardedRequests.set(requestModel.session.sessionResource, forwardRequest);
251
252
try {
253
await forwardRequest;
254
} finally {
255
this.pendingForwardedRequests.delete(requestModel.session.sessionResource);
256
}
257
}
258
259
private async doForwardRequestToChatWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {
260
const widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource);
261
const modeInfo = widget?.input.currentModeInfo;
262
263
// We need a signal to know when we can resend the request to
264
// Chat. Waiting for the registration of the agent is not
265
// enough, we also need a language/tools model to be available.
266
267
let agentActivated = false;
268
let agentReady = false;
269
let languageModelReady = false;
270
let toolsModelReady = false;
271
272
const whenAgentActivated = this.whenAgentActivated(chatService).then(() => agentActivated = true);
273
const whenAgentReady = this.whenAgentReady(chatAgentService, modeInfo?.kind)?.then(() => agentReady = true);
274
const whenLanguageModelReady = this.whenLanguageModelReady(languageModelsService, requestModel.modelId)?.then(() => languageModelReady = true);
275
const whenToolsModelReady = this.whenToolsModelReady(languageModelToolsService, requestModel)?.then(() => toolsModelReady = true);
276
277
if (whenLanguageModelReady instanceof Promise || whenAgentReady instanceof Promise || whenToolsModelReady instanceof Promise) {
278
const timeoutHandle = setTimeout(() => {
279
progress({
280
kind: 'progressMessage',
281
content: new MarkdownString(localize('waitingChat2', "Chat is almost ready...")),
282
});
283
}, 10000);
284
285
try {
286
const ready = await Promise.race([
287
timeout(this.environmentService.remoteAuthority ? 60000 /* increase for remote scenarios */ : 20000).then(() => 'timedout'),
288
Promise.allSettled([
289
whenAgentActivated,
290
whenAgentReady,
291
whenLanguageModelReady,
292
whenToolsModelReady
293
])
294
]);
295
296
if (ready === 'timedout') {
297
let warningMessage: string;
298
if (this.chatEntitlementService.anonymous) {
299
warningMessage = localize('chatTookLongWarningAnonymous', "Chat took too long to get ready. Please ensure that the extension `{0}` is installed and enabled.", defaultChat.chatExtensionId);
300
} else {
301
warningMessage = 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);
302
}
303
304
this.logService.warn(warningMessage, {
305
agentActivated,
306
agentReady,
307
languageModelReady,
308
toolsModelReady
309
});
310
311
progress({
312
kind: 'warning',
313
content: new MarkdownString(warningMessage)
314
});
315
316
// This means Chat is unhealthy and we cannot retry the
317
// request. Signal this to the outside via an event.
318
this._onUnresolvableError.fire();
319
return;
320
}
321
} finally {
322
clearTimeout(timeoutHandle);
323
}
324
}
325
326
await chatService.resendRequest(requestModel, {
327
...widget?.getModeRequestOptions(),
328
modeInfo,
329
userSelectedModelId: widget?.input.currentLanguageModel
330
});
331
}
332
333
private whenLanguageModelReady(languageModelsService: ILanguageModelsService, modelId: string | undefined): Promise<unknown> | void {
334
const hasModelForRequest = () => {
335
if (modelId) {
336
return !!languageModelsService.lookupLanguageModel(modelId);
337
}
338
339
for (const id of languageModelsService.getLanguageModelIds()) {
340
const model = languageModelsService.lookupLanguageModel(id);
341
if (model?.isDefault) {
342
return true;
343
}
344
}
345
346
return false;
347
};
348
349
if (hasModelForRequest()) {
350
return;
351
}
352
353
return Event.toPromise(Event.filter(languageModelsService.onDidChangeLanguageModels, () => hasModelForRequest()));
354
}
355
356
private whenToolsModelReady(languageModelToolsService: ILanguageModelToolsService, requestModel: IChatRequestModel): Promise<unknown> | void {
357
const needsToolsModel = requestModel.message.parts.some(part => part instanceof ChatRequestToolPart);
358
if (!needsToolsModel) {
359
return; // No tools in this request, no need to check
360
}
361
362
// check that tools other than setup. and internal tools are registered.
363
for (const tool of languageModelToolsService.getTools()) {
364
if (tool.id.startsWith('copilot_')) {
365
return; // we have tools!
366
}
367
}
368
369
return Event.toPromise(Event.filter(languageModelToolsService.onDidChangeTools, () => {
370
for (const tool of languageModelToolsService.getTools()) {
371
if (tool.id.startsWith('copilot_')) {
372
return true; // we have tools!
373
}
374
}
375
376
return false; // no external tools found
377
}));
378
}
379
380
private whenAgentReady(chatAgentService: IChatAgentService, mode: ChatModeKind | undefined): Promise<unknown> | void {
381
const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);
382
if (defaultAgent && !defaultAgent.isCore) {
383
return; // we have a default agent from an extension!
384
}
385
386
return Event.toPromise(Event.filter(chatAgentService.onDidChangeAgents, () => {
387
const defaultAgent = chatAgentService.getDefaultAgent(this.location, mode);
388
return Boolean(defaultAgent && !defaultAgent.isCore);
389
}));
390
}
391
392
private async whenAgentActivated(chatService: IChatService): Promise<void> {
393
try {
394
await chatService.activateDefaultAgent(this.location);
395
} catch (error) {
396
this.logService.error(error);
397
}
398
}
399
400
private async doInvokeWithSetup(request: IChatAgentRequest, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatWidgetService: IChatWidgetService, chatAgentService: IChatAgentService, languageModelToolsService: ILanguageModelToolsService): Promise<IChatAgentResult> {
401
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: CHAT_SETUP_ACTION_ID, from: 'chat' });
402
403
const widget = chatWidgetService.getWidgetBySessionResource(request.sessionResource);
404
const requestModel = widget?.viewModel?.model.getRequests().at(-1);
405
406
const setupListener = Event.runAndSubscribe(this.controller.value.onDidChange, (() => {
407
switch (this.controller.value.step) {
408
case ChatSetupStep.SigningIn:
409
progress({
410
kind: 'progressMessage',
411
content: new MarkdownString(localize('setupChatSignIn2', "Signing in to {0}...", ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.provider.enterprise.id ? defaultChat.provider.enterprise.name : defaultChat.provider.default.name)),
412
});
413
break;
414
case ChatSetupStep.Installing:
415
progress({
416
kind: 'progressMessage',
417
content: new MarkdownString(localize('installingChat', "Getting chat ready...")),
418
});
419
break;
420
}
421
}));
422
423
let result: IChatSetupResult | undefined = undefined;
424
try {
425
result = await ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({
426
disableChatViewReveal: true, // we are already in a chat context
427
forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithoutDialog : undefined // only enable anonymous selectively
428
});
429
} catch (error) {
430
this.logService.error(`[chat setup] Error during setup: ${toErrorMessage(error)}`);
431
} finally {
432
setupListener.dispose();
433
}
434
435
// User has agreed to run the setup
436
if (typeof result?.success === 'boolean') {
437
if (result.success) {
438
if (result.dialogSkipped) {
439
await widget?.clear(); // make room for the Chat welcome experience
440
} else if (requestModel) {
441
let newRequest = this.replaceAgentInRequestModel(requestModel, chatAgentService); // Replace agent part with the actual Chat agent...
442
newRequest = this.replaceToolInRequestModel(newRequest); // ...then replace any tool parts with the actual Chat tools
443
444
await this.forwardRequestToChat(newRequest, progress, chatService, languageModelsService, chatAgentService, chatWidgetService, languageModelToolsService);
445
}
446
} else {
447
progress({
448
kind: 'warning',
449
content: new MarkdownString(localize('chatSetupError', "Chat setup failed."))
450
});
451
}
452
}
453
454
// User has cancelled the setup
455
else {
456
progress({
457
kind: 'markdownContent',
458
content: this.workspaceTrustManagementService.isWorkspaceTrusted() ? SetupAgent.SETUP_NEEDED_MESSAGE : SetupAgent.TRUST_NEEDED_MESSAGE
459
});
460
}
461
462
return {};
463
}
464
465
private replaceAgentInRequestModel(requestModel: IChatRequestModel, chatAgentService: IChatAgentService): IChatRequestModel {
466
const agentPart = requestModel.message.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart);
467
if (!agentPart) {
468
return requestModel;
469
}
470
471
const agentId = agentPart.agent.id.replace(/setup\./, `${defaultChat.extensionId}.`.toLowerCase());
472
const githubAgent = chatAgentService.getAgent(agentId);
473
if (!githubAgent) {
474
return requestModel;
475
}
476
477
const newAgentPart = new ChatRequestAgentPart(agentPart.range, agentPart.editorRange, githubAgent);
478
479
return new ChatRequestModel({
480
session: requestModel.session as ChatModel,
481
message: {
482
parts: requestModel.message.parts.map(part => {
483
if (part instanceof ChatRequestAgentPart) {
484
return newAgentPart;
485
}
486
return part;
487
}),
488
text: requestModel.message.text
489
},
490
variableData: requestModel.variableData,
491
timestamp: Date.now(),
492
attempt: requestModel.attempt,
493
modeInfo: requestModel.modeInfo,
494
confirmation: requestModel.confirmation,
495
locationData: requestModel.locationData,
496
attachedContext: requestModel.attachedContext,
497
isCompleteAddedRequest: requestModel.isCompleteAddedRequest,
498
});
499
}
500
501
private replaceToolInRequestModel(requestModel: IChatRequestModel): IChatRequestModel {
502
const toolPart = requestModel.message.parts.find((r): r is ChatRequestToolPart => r instanceof ChatRequestToolPart);
503
if (!toolPart) {
504
return requestModel;
505
}
506
507
const toolId = toolPart.toolId.replace(/setup.tools\./, `copilot_`.toLowerCase());
508
const newToolPart = new ChatRequestToolPart(
509
toolPart.range,
510
toolPart.editorRange,
511
toolPart.toolName,
512
toolId,
513
toolPart.displayName,
514
toolPart.icon
515
);
516
517
const chatRequestToolEntry: IChatRequestToolEntry = {
518
id: toolId,
519
name: 'new',
520
range: toolPart.range,
521
kind: 'tool',
522
value: undefined
523
};
524
525
const variableData: IChatRequestVariableData = {
526
variables: [chatRequestToolEntry]
527
};
528
529
return new ChatRequestModel({
530
session: requestModel.session as ChatModel,
531
message: {
532
parts: requestModel.message.parts.map(part => {
533
if (part instanceof ChatRequestToolPart) {
534
return newToolPart;
535
}
536
return part;
537
}),
538
text: requestModel.message.text
539
},
540
variableData: variableData,
541
timestamp: Date.now(),
542
attempt: requestModel.attempt,
543
modeInfo: requestModel.modeInfo,
544
confirmation: requestModel.confirmation,
545
locationData: requestModel.locationData,
546
attachedContext: [chatRequestToolEntry],
547
isCompleteAddedRequest: requestModel.isCompleteAddedRequest,
548
});
549
}
550
}
551
552
export class SetupTool implements IToolImpl {
553
554
static registerTool(instantiationService: IInstantiationService, toolData: IToolData): IDisposable {
555
return instantiationService.invokeFunction(accessor => {
556
const toolService = accessor.get(ILanguageModelToolsService);
557
558
const tool = instantiationService.createInstance(SetupTool);
559
return toolService.registerTool(toolData, tool);
560
});
561
}
562
563
async invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult> {
564
const result: IToolResult = {
565
content: [
566
{
567
kind: 'text',
568
value: ''
569
}
570
]
571
};
572
573
return result;
574
}
575
576
async prepareToolInvocation?(parameters: unknown, token: CancellationToken): Promise<IPreparedToolInvocation | undefined> {
577
return undefined;
578
}
579
}
580
581
export class AINewSymbolNamesProvider {
582
583
static registerProvider(instantiationService: IInstantiationService, context: ChatEntitlementContext, controller: Lazy<ChatSetupController>): IDisposable {
584
return instantiationService.invokeFunction(accessor => {
585
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
586
587
const provider = instantiationService.createInstance(AINewSymbolNamesProvider, context, controller);
588
return languageFeaturesService.newSymbolNamesProvider.register('*', provider);
589
});
590
}
591
592
constructor(
593
private readonly context: ChatEntitlementContext,
594
private readonly controller: Lazy<ChatSetupController>,
595
@IInstantiationService private readonly instantiationService: IInstantiationService,
596
@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService,
597
) {
598
}
599
600
async provideNewSymbolNames(model: ITextModel, range: IRange, triggerKind: NewSymbolNameTriggerKind, token: CancellationToken): Promise<NewSymbolName[] | undefined> {
601
await this.instantiationService.invokeFunction(accessor => {
602
return ChatSetup.getInstance(this.instantiationService, this.context, this.controller).run({
603
forceAnonymous: this.chatEntitlementService.anonymous ? ChatSetupAnonymous.EnabledWithDialog : undefined
604
});
605
});
606
607
return [];
608
}
609
}
610
611
export class ChatCodeActionsProvider {
612
613
static registerProvider(instantiationService: IInstantiationService): IDisposable {
614
return instantiationService.invokeFunction(accessor => {
615
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
616
617
const provider = instantiationService.createInstance(ChatCodeActionsProvider);
618
return languageFeaturesService.codeActionProvider.register('*', provider);
619
});
620
}
621
622
constructor(
623
@IMarkerService private readonly markerService: IMarkerService,
624
) {
625
}
626
627
async provideCodeActions(model: ITextModel, range: Range | Selection): Promise<CodeActionList | undefined> {
628
const actions: CodeAction[] = [];
629
630
// "Generate" if the line is whitespace only
631
// "Modify" if there is a selection
632
let generateOrModifyTitle: string | undefined;
633
let generateOrModifyCommand: Command | undefined;
634
if (range.isEmpty()) {
635
const textAtLine = model.getLineContent(range.startLineNumber);
636
if (/^\s*$/.test(textAtLine)) {
637
generateOrModifyTitle = localize('generate', "Generate");
638
generateOrModifyCommand = AICodeActionsHelper.generate(range);
639
}
640
} else {
641
const textInSelection = model.getValueInRange(range);
642
if (!/^\s*$/.test(textInSelection)) {
643
generateOrModifyTitle = localize('modify', "Modify");
644
generateOrModifyCommand = AICodeActionsHelper.modify(range);
645
}
646
}
647
648
if (generateOrModifyTitle && generateOrModifyCommand) {
649
actions.push({
650
kind: CodeActionKind.RefactorRewrite.append('copilot').value,
651
isAI: true,
652
title: generateOrModifyTitle,
653
command: generateOrModifyCommand,
654
});
655
}
656
657
const markers = AICodeActionsHelper.warningOrErrorMarkersAtRange(this.markerService, model.uri, range);
658
if (markers.length > 0) {
659
660
// "Fix" if there are diagnostics in the range
661
actions.push({
662
kind: CodeActionKind.QuickFix.append('copilot').value,
663
isAI: true,
664
diagnostics: markers,
665
title: localize('fix', "Fix"),
666
command: AICodeActionsHelper.fixMarkers(markers, range)
667
});
668
669
// "Explain" if there are diagnostics in the range
670
actions.push({
671
kind: CodeActionKind.QuickFix.append('explain').append('copilot').value,
672
isAI: true,
673
diagnostics: markers,
674
title: localize('explain', "Explain"),
675
command: AICodeActionsHelper.explainMarkers(markers)
676
});
677
}
678
679
return {
680
actions,
681
dispose() { }
682
};
683
}
684
}
685
686
export class AICodeActionsHelper {
687
688
static warningOrErrorMarkersAtRange(markerService: IMarkerService, resource: URI, range: Range | Selection): IMarker[] {
689
return markerService
690
.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning })
691
.filter(marker => range.startLineNumber <= marker.endLineNumber && range.endLineNumber >= marker.startLineNumber);
692
}
693
694
static modify(range: Range): Command {
695
return {
696
id: INLINE_CHAT_START,
697
title: localize('modify', "Modify"),
698
arguments: [
699
{
700
initialSelection: this.rangeToSelection(range),
701
initialRange: range,
702
position: range.getStartPosition()
703
} satisfies { initialSelection: ISelection; initialRange: IRange; position: IPosition }
704
]
705
};
706
}
707
708
static generate(range: Range): Command {
709
return {
710
id: INLINE_CHAT_START,
711
title: localize('generate', "Generate"),
712
arguments: [
713
{
714
initialSelection: this.rangeToSelection(range),
715
initialRange: range,
716
position: range.getStartPosition()
717
} satisfies { initialSelection: ISelection; initialRange: IRange; position: IPosition }
718
]
719
};
720
}
721
722
private static rangeToSelection(range: Range): ISelection {
723
return new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
724
}
725
726
static explainMarkers(markers: IMarker[]): Command {
727
return {
728
id: CHAT_OPEN_ACTION_ID,
729
title: localize('explain', "Explain"),
730
arguments: [
731
{
732
query: `@workspace /explain ${markers.map(marker => marker.message).join(', ')}`,
733
isPartialQuery: true
734
} satisfies { query: string; isPartialQuery: boolean }
735
]
736
};
737
}
738
739
static fixMarkers(markers: IMarker[], range: Range): Command {
740
return {
741
id: INLINE_CHAT_START,
742
title: localize('fix', "Fix"),
743
arguments: [
744
{
745
message: `/fix ${markers.map(marker => marker.message).join(', ')}`,
746
initialSelection: this.rangeToSelection(range),
747
initialRange: range,
748
position: range.getStartPosition()
749
} satisfies { message: string; initialSelection: ISelection; initialRange: IRange; position: IPosition }
750
]
751
};
752
}
753
}
754
755