Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/languageModelToolsService.ts
3296 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 { Separator } from '../../../../base/common/actions.js';
7
import { VSBuffer } from '../../../../base/common/buffer.js';
8
import { CancellationToken } from '../../../../base/common/cancellation.js';
9
import { Event } from '../../../../base/common/event.js';
10
import { IMarkdownString } from '../../../../base/common/htmlContent.js';
11
import { Iterable } from '../../../../base/common/iterator.js';
12
import { IJSONSchema } from '../../../../base/common/jsonSchema.js';
13
import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
14
import { Schemas } from '../../../../base/common/network.js';
15
import { derived, IObservable, IReader, ITransaction, ObservableSet } from '../../../../base/common/observable.js';
16
import { ThemeIcon } from '../../../../base/common/themables.js';
17
import { URI } from '../../../../base/common/uri.js';
18
import { Location } from '../../../../editor/common/languages.js';
19
import { localize } from '../../../../nls.js';
20
import { ContextKeyExpression } 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 { IProgress } from '../../../../platform/progress/common/progress.js';
24
import { IVariableReference } from './chatModes.js';
25
import { IChatExtensionsContent, IChatTodoListContent, IChatToolInputInvocationData, type IChatTerminalToolInvocationData } from './chatService.js';
26
import { ChatRequestToolReferenceEntry } from './chatVariableEntries.js';
27
import { LanguageModelPartAudience } from './languageModels.js';
28
import { PromptElementJSON, stringifyPromptElementJSON } from './tools/promptTsxTypes.js';
29
30
export interface IToolData {
31
id: string;
32
source: ToolDataSource;
33
toolReferenceName?: string;
34
icon?: { dark: URI; light?: URI } | ThemeIcon;
35
when?: ContextKeyExpression;
36
tags?: string[];
37
displayName: string;
38
userDescription?: string;
39
modelDescription: string;
40
inputSchema?: IJSONSchema;
41
canBeReferencedInPrompt?: boolean;
42
/**
43
* True if the tool runs in the (possibly remote) workspace, false if it runs
44
* on the host, undefined if known.
45
*/
46
runsInWorkspace?: boolean;
47
alwaysDisplayInputOutput?: boolean;
48
}
49
50
export interface IToolProgressStep {
51
readonly message: string | IMarkdownString | undefined;
52
readonly increment?: number;
53
readonly total?: number;
54
}
55
56
export type ToolProgress = IProgress<IToolProgressStep>;
57
58
export type ToolDataSource =
59
| {
60
type: 'extension';
61
label: string;
62
extensionId: ExtensionIdentifier;
63
}
64
| {
65
type: 'mcp';
66
label: string;
67
serverLabel: string | undefined;
68
instructions: string | undefined;
69
collectionId: string;
70
definitionId: string;
71
}
72
| {
73
type: 'user';
74
label: string;
75
file: URI;
76
}
77
| {
78
type: 'internal';
79
label: string;
80
} | {
81
type: 'external';
82
label: string;
83
};
84
85
export namespace ToolDataSource {
86
87
export const Internal: ToolDataSource = { type: 'internal', label: 'Built-In' };
88
89
/** External tools may not be contributed or invoked, but may be invoked externally and described in an IChatToolInvocationSerialized */
90
export const External: ToolDataSource = { type: 'external', label: 'External' };
91
92
export function toKey(source: ToolDataSource): string {
93
switch (source.type) {
94
case 'extension': return `extension:${source.extensionId.value}`;
95
case 'mcp': return `mcp:${source.collectionId}:${source.definitionId}`;
96
case 'user': return `user:${source.file.toString()}`;
97
case 'internal': return 'internal';
98
case 'external': return 'external';
99
}
100
}
101
102
export function equals(a: ToolDataSource, b: ToolDataSource): boolean {
103
return toKey(a) === toKey(b);
104
}
105
106
export function classify(source: ToolDataSource): { readonly ordinal: number; readonly label: string } {
107
if (source.type === 'internal') {
108
return { ordinal: 1, label: localize('builtin', 'Built-In') };
109
} else if (source.type === 'mcp') {
110
return { ordinal: 2, label: localize('mcp', 'MCP Server: {0}', source.label) };
111
} else if (source.type === 'user') {
112
return { ordinal: 0, label: localize('user', 'User Defined') };
113
} else {
114
return { ordinal: 3, label: localize('ext', 'Extension: {0}', source.label) };
115
}
116
}
117
}
118
119
export interface IToolInvocation {
120
callId: string;
121
toolId: string;
122
parameters: Object;
123
tokenBudget?: number;
124
context: IToolInvocationContext | undefined;
125
chatRequestId?: string;
126
chatInteractionId?: string;
127
toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatTodoListContent;
128
modelId?: string;
129
}
130
131
export interface IToolInvocationContext {
132
sessionId: string;
133
}
134
135
export function isToolInvocationContext(obj: any): obj is IToolInvocationContext {
136
return typeof obj === 'object' && typeof obj.sessionId === 'string';
137
}
138
139
export interface IToolInvocationPreparationContext {
140
parameters: any;
141
chatRequestId?: string;
142
chatSessionId?: string;
143
chatInteractionId?: string;
144
}
145
146
export type ToolInputOutputBase = {
147
/** Mimetype of the value, optional */
148
mimeType?: string;
149
/** URI of the resource on the MCP server. */
150
uri?: URI;
151
/** If true, this part came in as a resource reference rather than direct data. */
152
asResource?: boolean;
153
};
154
155
export type ToolInputOutputEmbedded = ToolInputOutputBase & {
156
type: 'embed';
157
value: string;
158
/** If true, value is text. If false or not given, value is base64 */
159
isText?: boolean;
160
};
161
162
export type ToolInputOutputReference = ToolInputOutputBase & { type: 'ref'; uri: URI };
163
164
export interface IToolResultInputOutputDetails {
165
readonly input: string;
166
readonly output: (ToolInputOutputEmbedded | ToolInputOutputReference)[];
167
readonly isError?: boolean;
168
}
169
170
export interface IToolResultOutputDetails {
171
readonly output: { type: 'data'; mimeType: string; value: VSBuffer };
172
}
173
174
export function isToolResultInputOutputDetails(obj: any): obj is IToolResultInputOutputDetails {
175
return typeof obj === 'object' && typeof obj?.input === 'string' && (typeof obj?.output === 'string' || Array.isArray(obj?.output));
176
}
177
178
export function isToolResultOutputDetails(obj: any): obj is IToolResultOutputDetails {
179
return typeof obj === 'object' && typeof obj?.output === 'object' && typeof obj?.output?.mimeType === 'string' && obj?.output?.type === 'data';
180
}
181
182
export interface IToolResult {
183
content: (IToolResultPromptTsxPart | IToolResultTextPart | IToolResultDataPart)[];
184
toolResultMessage?: string | IMarkdownString;
185
toolResultDetails?: Array<URI | Location> | IToolResultInputOutputDetails | IToolResultOutputDetails;
186
toolResultError?: string;
187
}
188
189
export function toolResultHasBuffers(result: IToolResult): boolean {
190
return result.content.some(part => part.kind === 'data');
191
}
192
193
export interface IToolResultPromptTsxPart {
194
kind: 'promptTsx';
195
value: unknown;
196
}
197
198
export function stringifyPromptTsxPart(part: IToolResultPromptTsxPart): string {
199
return stringifyPromptElementJSON(part.value as PromptElementJSON);
200
}
201
202
export interface IToolResultTextPart {
203
kind: 'text';
204
value: string;
205
audience?: LanguageModelPartAudience[];
206
}
207
208
export interface IToolResultDataPart {
209
kind: 'data';
210
value: {
211
mimeType: string;
212
data: VSBuffer;
213
};
214
audience?: LanguageModelPartAudience[];
215
}
216
217
export interface IToolConfirmationMessages {
218
title: string | IMarkdownString;
219
message: string | IMarkdownString;
220
disclaimer?: string | IMarkdownString;
221
allowAutoConfirm?: boolean;
222
terminalCustomActions?: ToolConfirmationAction[];
223
}
224
225
export interface IToolConfirmationAction {
226
label: string;
227
disabled?: boolean;
228
tooltip?: string;
229
data: any;
230
}
231
232
export type ToolConfirmationAction = IToolConfirmationAction | Separator;
233
234
export enum ToolInvocationPresentation {
235
Hidden = 'hidden',
236
HiddenAfterComplete = 'hiddenAfterComplete'
237
}
238
239
export interface IPreparedToolInvocation {
240
invocationMessage?: string | IMarkdownString;
241
pastTenseMessage?: string | IMarkdownString;
242
originMessage?: string | IMarkdownString;
243
confirmationMessages?: IToolConfirmationMessages;
244
presentation?: ToolInvocationPresentation;
245
toolSpecificData?: IChatTerminalToolInvocationData | IChatToolInputInvocationData | IChatExtensionsContent | IChatTodoListContent;
246
}
247
248
export interface IToolImpl {
249
invoke(invocation: IToolInvocation, countTokens: CountTokensCallback, progress: ToolProgress, token: CancellationToken): Promise<IToolResult>;
250
prepareToolInvocation?(context: IToolInvocationPreparationContext, token: CancellationToken): Promise<IPreparedToolInvocation | undefined>;
251
}
252
253
export type IToolAndToolSetEnablementMap = ReadonlyMap<IToolData | ToolSet, boolean>;
254
255
export class ToolSet {
256
257
protected readonly _tools = new ObservableSet<IToolData>();
258
259
protected readonly _toolSets = new ObservableSet<ToolSet>();
260
261
/**
262
* A homogenous tool set only contains tools from the same source as the tool set itself
263
*/
264
readonly isHomogenous: IObservable<boolean>;
265
266
constructor(
267
readonly id: string,
268
readonly referenceName: string,
269
readonly icon: ThemeIcon,
270
readonly source: ToolDataSource,
271
readonly description?: string,
272
) {
273
274
this.isHomogenous = derived(r => {
275
return !Iterable.some(this._tools.observable.read(r), tool => !ToolDataSource.equals(tool.source, this.source))
276
&& !Iterable.some(this._toolSets.observable.read(r), toolSet => !ToolDataSource.equals(toolSet.source, this.source));
277
});
278
}
279
280
addTool(data: IToolData, tx?: ITransaction): IDisposable {
281
this._tools.add(data, tx);
282
return toDisposable(() => {
283
this._tools.delete(data);
284
});
285
}
286
287
addToolSet(toolSet: ToolSet, tx?: ITransaction): IDisposable {
288
if (toolSet === this) {
289
return Disposable.None;
290
}
291
this._toolSets.add(toolSet, tx);
292
return toDisposable(() => {
293
this._toolSets.delete(toolSet);
294
});
295
}
296
297
getTools(r?: IReader): Iterable<IToolData> {
298
return Iterable.concat(
299
this._tools.observable.read(r),
300
...Iterable.map(this._toolSets.observable.read(r), toolSet => toolSet.getTools(r))
301
);
302
}
303
}
304
305
306
export const ILanguageModelToolsService = createDecorator<ILanguageModelToolsService>('ILanguageModelToolsService');
307
308
export type CountTokensCallback = (input: string, token: CancellationToken) => Promise<number>;
309
310
export interface ILanguageModelToolsService {
311
_serviceBrand: undefined;
312
onDidChangeTools: Event<void>;
313
registerToolData(toolData: IToolData): IDisposable;
314
registerToolImplementation(id: string, tool: IToolImpl): IDisposable;
315
registerTool(toolData: IToolData, tool: IToolImpl): IDisposable;
316
getTools(): Iterable<Readonly<IToolData>>;
317
getTool(id: string): IToolData | undefined;
318
getToolByName(name: string, includeDisabled?: boolean): IToolData | undefined;
319
invokeTool(invocation: IToolInvocation, countTokens: CountTokensCallback, token: CancellationToken): Promise<IToolResult>;
320
setToolAutoConfirmation(toolId: string, scope: 'workspace' | 'profile' | 'session' | 'never'): void;
321
getToolAutoConfirmation(toolId: string): 'workspace' | 'profile' | 'session' | 'never';
322
resetToolAutoConfirmation(): void;
323
cancelToolCallsForRequest(requestId: string): void;
324
toToolEnablementMap(toolOrToolSetNames: Set<string>): Record<string, boolean>;
325
toToolAndToolSetEnablementMap(toolOrToolSetNames: readonly string[]): IToolAndToolSetEnablementMap;
326
toToolReferences(variableReferences: readonly IVariableReference[]): ChatRequestToolReferenceEntry[];
327
328
readonly toolSets: IObservable<Iterable<ToolSet>>;
329
getToolSet(id: string): ToolSet | undefined;
330
getToolSetByName(name: string): ToolSet | undefined;
331
createToolSet(source: ToolDataSource, id: string, referenceName: string, options?: { icon?: ThemeIcon; description?: string }): ToolSet & IDisposable;
332
}
333
334
export function createToolInputUri(toolOrId: IToolData | string): URI {
335
if (typeof toolOrId !== 'string') {
336
toolOrId = toolOrId.id;
337
}
338
return URI.from({ scheme: Schemas.inMemory, path: `/lm/tool/${toolOrId}/tool_input.json` });
339
}
340
341
export function createToolSchemaUri(toolOrId: IToolData | string): URI {
342
if (typeof toolOrId !== 'string') {
343
toolOrId = toolOrId.id;
344
}
345
return URI.from({ scheme: Schemas.vscode, authority: 'schemas', path: `/lm/tool/${toolOrId}` });
346
}
347
348