1
>
/*---------------------------------------------------------------------------------------------
chatAgents.ts
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 { findLast } from '../../../../../base/common/arraysFind.js';
7
>
import { CancellationToken } from '../../../../../base/common/cancellation.js';
8
>
import { IStringDictionary } from '../../../../../base/common/collections.js';
9
>
import { Emitter, Event } from '../../../../../base/common/event.js';
10
>
import { IMarkdownString } from '../../../../../base/common/htmlContent.js';
11
>
import { Iterable } from '../../../../../base/common/iterator.js';
12
>
import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
13
>
import { revive, Revived } from '../../../../../base/common/marshalling.js';
14
>
import { IObservable } from '../../../../../base/common/observable.js';
15
>
import { equalsIgnoreCase } from '../../../../../base/common/strings.js';
16
>
import { ThemeIcon } from '../../../../../base/common/themables.js';
17
>
import { URI } from '../../../../../base/common/uri.js';
18
>
import { Command } from '../../../../../editor/common/languages.js';
19
>
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
20
>
import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
21
>
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
22
>
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
23
>
import { ChatContextKeys } from '../actions/chatContextKeys.js';
24
>
import { IChatAgentEditedFileEvent, IChatProgressHistoryResponseContent, IChatRequestModeInstructions, IChatRequestVariableData, ISerializableChatAgentData } from '../model/chatModel.js';
25
>
import { ChatRequestHooks } from '../promptSyntax/hookSchema.js';
26
>
import { IRawChatCommandContribution } from './chatParticipantContribTypes.js';
27
>
import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from '../chatService/chatService.js';
28
>
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ChatPermissionLevel } from '../constants.js';
29
>
import { ILanguageModelsService } from '../languageModels.js';
30
>
import { ChatPerfMark, markChat } from '../chatPerf.js';
31
>
32
>
//#region agent service, commands etc
33
>
34
>
export interface IChatAgentHistoryEntry {
35
>
request: IChatAgentRequest;
36
>
response: ReadonlyArray<IChatProgressHistoryResponseContent | IChatTaskDto>;
37
>
result: IChatAgentResult;
38
>
}
39
>
40
>
export interface IChatAgentAttachmentCapabilities {
41
>
supportsFileAttachments?: boolean;
42
>
supportsToolAttachments?: boolean;
43
>
supportsMCPAttachments?: boolean;
44
>
supportsImageAttachments?: boolean;
45
>
supportsSearchResultAttachments?: boolean;
46
>
supportsInstructionAttachments?: boolean;
47
>
supportsSourceControlAttachments?: boolean;
48
>
supportsProblemAttachments?: boolean;
49
>
supportsSymbolAttachments?: boolean;
50
>
supportsTerminalAttachments?: boolean;
51
>
supportsPromptAttachments?: boolean;
52
>
supportsHandOffs?: boolean;
53
>
supportsCheckpoints?: boolean;
54
>
/**
55
>
* The prefix (e.g. `!`) that marks a message in this
56
>
* session type as a terminal command rather than a message to the agent.
57
>
* Undefined when the session type has no terminal command support.
58
>
*/
59
>
terminalCommandPrefix?: string;
60
>
}
61
>
62
>
export interface IChatAgentData {
63
>
id: string;
64
>
name: string;
65
>
fullName?: string;
66
>
description?: string;
67
>
/** This is string, not ContextKeyExpression, because dealing with serializing/deserializing is hard and need a better pattern for this */
68
>
when?: string;
69
>
extensionId: ExtensionIdentifier;
70
>
extensionVersion: string | undefined;
71
>
extensionPublisherId: string;
72
>
/** This is the extension publisher id, or, in the case of a dynamically registered participant (remote agent), whatever publisher name we have for it */
73
>
publisherDisplayName?: string;
74
>
extensionDisplayName: string;
75
>
/** The agent invoked when no agent is specified */
76
>
isDefault?: boolean;
77
>
/** This agent is not contributed in package.json, but is registered dynamically */
78
>
isDynamic?: boolean;
79
>
/** This agent is contributed from core and not from an extension */
80
>
isCore?: boolean;
81
>
canAccessPreviousChatHistory?: boolean;
82
>
metadata: IChatAgentMetadata;
83
>
slashCommands: IChatAgentCommand[];
84
>
locations: ChatAgentLocation[];
85
>
/** This is only relevant for isDefault agents. Others should have all modes available. */
86
>
modes: ChatModeKind[];
87
>
disambiguation: { category: string; description: string; examples: string[] }[];
88
>
capabilities?: IChatAgentAttachmentCapabilities;
89
>
}
90
>
91
>
export interface IChatWelcomeMessageContent {
92
>
icon: ThemeIcon;
93
>
title: string;
94
>
message: IMarkdownString;
95
>
}
96
>
97
>
export interface IChatAgentImplementation {
98
>
invoke(request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
99
>
setRequestTools?(requestId: string, tools: UserSelectedTools): void;
100
>
setYieldRequested?(requestId: string, value: boolean): void;
101
>
provideFollowups?(request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]>;
102
>
provideChatTitle?: (history: IChatAgentHistoryEntry[], token: CancellationToken) => Promise<string | undefined>;
103
>
provideChatSummary?: (history: IChatAgentHistoryEntry[], token: CancellationToken) => Promise<string | undefined>;
104
>
}
105
>
106
>
export interface IChatParticipantDetectionResult {
107
>
participant: string;
108
>
command?: string;
109
>
}
110
>
111
>
export interface IChatParticipantMetadata {
112
>
participant: string;
113
>
command?: string;
114
>
disambiguation: { category: string; description: string; examples: string[] }[];
115
>
}
116
>
117
>
export interface IChatParticipantDetectionProvider {
118
>
provideParticipantDetection(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation; participants: IChatParticipantMetadata[] }, token: CancellationToken): Promise<IChatParticipantDetectionResult | null | undefined>;
119
>
}
120
>
121
>
export type IChatAgent = IChatAgentData & IChatAgentImplementation;
122
>
123
>
export interface IChatAgentCommand extends IRawChatCommandContribution {
124
>
followupPlaceholder?: string;
125
>
}
126
>
127
>
export interface IChatAgentMetadata {
128
>
helpTextPrefix?: string | IMarkdownString;
129
>
helpTextPostfix?: string | IMarkdownString;
130
>
icon?: URI;
131
>
iconDark?: URI;
132
>
themeIcon?: ThemeIcon;
133
>
sampleRequest?: string;
134
>
supportIssueReporting?: boolean;
135
>
followupPlaceholder?: string;
136
>
isSticky?: boolean;
137
>
additionalWelcomeMessage?: string | IMarkdownString;
138
>
}
139
>
140
>
export type UserSelectedTools = Record<string, boolean>;
141
>
142
>
143
>
export interface IChatAgentRequest {
144
>
sessionResource: URI;
145
>
requestId: string;
146
>
agentId: string;
147
>
command?: string;
148
>
message: string;
149
>
attempt?: number;
150
>
enableCommandDetection?: boolean;
151
>
isParticipantDetected?: boolean;
152
>
variables: IChatRequestVariableData;
153
>
location: ChatAgentLocation;
154
>
locationData?: Revived<IChatLocationData>;
155
>
acceptedConfirmationData?: unknown[];
156
>
rejectedConfirmationData?: unknown[];
157
>
agentHostSessionConfig?: Record<string, unknown>;
158
>
userSelectedModelId?: string;
159
>
modelConfiguration?: IStringDictionary<unknown>;
160
>
userSelectedTools?: UserSelectedTools;
161
>
modeInstructions?: IChatRequestModeInstructions;
162
>
editedFileEvents?: IChatAgentEditedFileEvent[];
163
>
/**
164
>
* The working directory URI for the session, if set.
165
>
* In the agents window, each session can have its own working directory
166
>
* that differs from the current workspace folders.
167
>
*/
168
>
workingDirectory?: URI;
169
>
/**
170
>
* Collected hooks configuration for this request.
171
>
* Contains all hooks defined in hooks .json files, organized by hook type.
172
>
*/
173
>
hooks?: ChatRequestHooks;
174
>
/**
175
>
* Whether any hooks are enabled for this request.
176
>
*/
177
>
hasHooksEnabled?: boolean;
178
>
/**
179
>
* The permission level for tool auto-approval in this request.
180
>
* - `'autoApprove'`: Auto-approve all tool calls and retry on errors.
181
>
* - `'autopilot'`: Everything autoApprove does plus continues until the task is done.
182
>
*/
183
>
permissionLevel?: ChatPermissionLevel;
184
>
/**
185
>
* Unique ID for the subagent invocation, used to group tool calls from the same subagent run together.
186
>
*/
187
>
subAgentInvocationId?: string;
188
>
/**
189
>
* Display name of the subagent that is invoking this request.
190
>
*/
191
>
subAgentName?: string;
192
>
/**
193
>
* The request ID of the parent request that invoked this subagent.
194
>
*/
195
>
parentRequestId?: string;
196
>
197
>
/**
198
>
* When true, this request was initiated by the system rather than the user.
199
>
*/
200
>
isSystemInitiated?: boolean;
201
>
}
202
>
203
>
export interface IChatQuestion {
204
>
readonly prompt: string;
205
>
readonly participant?: string;
206
>
readonly command?: string;
207
>
}
208
>
209
>
export interface IChatAgentResultTimings {
210
>
firstProgress?: number;
211
>
totalElapsed: number;
212
>
}
213
>
214
>
export interface IChatAgentResult {
215
>
errorDetails?: IChatResponseErrorDetails;
216
>
timings?: IChatAgentResultTimings;
217
>
/** Extra properties that the agent can use to identify a result */
218
>
readonly metadata?: { readonly [key: string]: unknown };
219
>
readonly details?: string;
220
>
nextQuestion?: IChatQuestion;
221
>
}
222
>
223
>
export const IChatAgentService = createDecorator<IChatAgentService>('chatAgentService');
224
>
225
>
interface IChatAgentEntry {
226
>
data: IChatAgentData;
227
>
impl?: IChatAgentImplementation;
228
>
}
229
>
230
>
export interface IChatAgentCompletionItem {
231
>
id: string;
232
>
name?: string;
233
>
fullName?: string;
234
>
icon?: ThemeIcon;
235
>
value: unknown;
236
>
command?: Command;
237
>
}
238
>
239
>
export interface IChatAgentInvocationEvent {
240
>
readonly agentId: string;
241
>
readonly request: Readonly<IChatAgentRequest>;
242
>
}
243
>
244
>
export interface IChatAgentService {
245
>
_serviceBrand: undefined;
246
>
/**
247
>
* undefined when an agent was removed
248
>
*/
249
>
readonly onDidChangeAgents: Event<IChatAgent | undefined>;
250
>
readonly onWillInvokeAgent: Event<IChatAgentInvocationEvent>;
251
>
readonly hasToolsAgent: boolean;
252
>
registerAgent(id: string, data: IChatAgentData): IDisposable;
253
>
registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable;
254
>
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable;
255
>
registerAgentCompletionProvider(id: string, provider: (query: string, token: CancellationToken) => Promise<IChatAgentCompletionItem[]>): IDisposable;
256
>
getAgentCompletionItems(id: string, query: string, token: CancellationToken): Promise<IChatAgentCompletionItem[]>;
257
>
registerChatParticipantDetectionProvider(handle: number, provider: IChatParticipantDetectionProvider): IDisposable;
258
>
detectAgentOrCommand(request: IChatAgentRequest, history: IChatAgentHistoryEntry[], options: { location: ChatAgentLocation }, token: CancellationToken): Promise<{ agent: IChatAgentData; command?: IChatAgentCommand } | undefined>;
259
>
hasChatParticipantDetectionProviders(): boolean;
260
>
invokeAgent(agent: string, request: IChatAgentRequest, progress: (parts: IChatProgress[]) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
261
>
setRequestTools(agent: string, requestId: string, tools: UserSelectedTools): void;
262
>
setYieldRequested(agent: string, requestId: string, value: boolean): void;
263
>
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]>;
264
>
getChatTitle(id: string, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<string | undefined>;
265
>
getChatSummary(id: string, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<string | undefined>;
266
>
getAgent(id: string, includeDisabled?: boolean): IChatAgentData | undefined;
267
>
getAgentByFullyQualifiedId(id: string): IChatAgentData | undefined;
268
>
getAgents(): IChatAgentData[];
269
>
getActivatedAgents(): Array<IChatAgent>;
270
>
getAgentsByName(name: string): IChatAgentData[];
271
>
agentHasDupeName(id: string): boolean;
272
>
273
>
/**
274
>
* Get the default agent (only if activated)
275
>
*/
276
>
getDefaultAgent(location: ChatAgentLocation, mode?: ChatModeKind): IChatAgent | undefined;
277
>
278
>
/**
279
>
* Get the default agent data that has been contributed (may not be activated yet)
280
>
*/
281
>
getContributedDefaultAgent(location: ChatAgentLocation): IChatAgentData | undefined;
282
>
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void;
283
>
}
284
>
285
>
export class ChatAgentService extends Disposable implements IChatAgentService {
286
>
287
>
public static readonly AGENT_LEADER = '@';
288
>
289
>
declare _serviceBrand: undefined;
290
>
291
>
private _agents = new Map<string, IChatAgentEntry>();
292
>
293
>
private readonly _onDidChangeAgents = this._register(new Emitter<IChatAgent | undefined>());
294
>
readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event;
295
>
private readonly _onWillInvokeAgent = this._register(new Emitter<IChatAgentInvocationEvent>());
296
>
readonly onWillInvokeAgent: Event<IChatAgentInvocationEvent> = this._onWillInvokeAgent.event;
297
>
298
>
private readonly _agentsContextKeys = new Set<string>();
299
>
private readonly _hasDefaultAgent: IContextKey<boolean>;
300
>
private readonly _extensionAgentRegistered: IContextKey<boolean>;
301
>
private readonly _defaultAgentRegistered: IContextKey<boolean>;
302
>
private _hasToolsAgent = false;
303
>
304
>
private _chatParticipantDetectionProviders = new Map<number, IChatParticipantDetectionProvider>();
305
>
306
>
constructor(
307
@IContextKeyService private readonly contextKeyService: IContextKeyService,
308
@IConfigurationService private readonly configurationService: IConfigurationService,