Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/chatVariableEntries.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 { Codicon } from '../../../../base/common/codicons.js';
7
import { basename } from '../../../../base/common/resources.js';
8
import { ThemeIcon } from '../../../../base/common/themables.js';
9
import { URI } from '../../../../base/common/uri.js';
10
import { IRange } from '../../../../editor/common/core/range.js';
11
import { IOffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js';
12
import { isLocation, Location, SymbolKind } from '../../../../editor/common/languages.js';
13
import { localize } from '../../../../nls.js';
14
import { MarkerSeverity, IMarker } from '../../../../platform/markers/common/markers.js';
15
import { ISCMHistoryItem } from '../../scm/common/history.js';
16
import { IChatContentReference } from './chatService.js';
17
import { IChatRequestVariableValue } from './chatVariables.js';
18
import { IToolData, ToolSet } from './languageModelToolsService.js';
19
20
21
interface IBaseChatRequestVariableEntry {
22
readonly id: string;
23
readonly fullName?: string;
24
readonly icon?: ThemeIcon;
25
readonly name: string;
26
readonly modelDescription?: string;
27
28
/**
29
* The offset-range in the prompt. This means this entry has been explicitly typed out
30
* by the user.
31
*/
32
readonly range?: IOffsetRange;
33
readonly value: IChatRequestVariableValue;
34
readonly references?: IChatContentReference[];
35
36
omittedState?: OmittedState;
37
}
38
39
export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry {
40
kind: 'generic';
41
}
42
43
export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry {
44
kind: 'directory';
45
}
46
47
export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry {
48
kind: 'file';
49
}
50
51
export const enum OmittedState {
52
NotOmitted,
53
Partial,
54
Full,
55
}
56
57
export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry {
58
readonly kind: 'tool';
59
}
60
61
export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry {
62
readonly kind: 'toolset';
63
readonly value: IChatRequestToolEntry[];
64
}
65
66
export type ChatRequestToolReferenceEntry = IChatRequestToolEntry | IChatRequestToolSetEntry;
67
68
export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry {
69
readonly kind: 'implicit';
70
readonly isFile: true;
71
readonly value: URI | Location | undefined;
72
readonly isSelection: boolean;
73
enabled: boolean;
74
}
75
76
export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry {
77
readonly kind: 'paste';
78
readonly code: string;
79
readonly language: string;
80
readonly pastedLines: string;
81
82
// This is only used for old serialized data and should be removed once we no longer support it
83
readonly fileName: string;
84
85
// This is only undefined on old serialized data
86
readonly copiedFrom: {
87
readonly uri: URI;
88
readonly range: IRange;
89
} | undefined;
90
}
91
92
export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry {
93
readonly kind: 'symbol';
94
readonly value: Location;
95
readonly symbolKind: SymbolKind;
96
}
97
98
export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry {
99
readonly kind: 'command';
100
}
101
102
export interface IImageVariableEntry extends IBaseChatRequestVariableEntry {
103
readonly kind: 'image';
104
readonly isPasted?: boolean;
105
readonly isURL?: boolean;
106
readonly mimeType?: string;
107
}
108
109
export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry {
110
readonly kind: 'notebookOutput';
111
readonly outputIndex?: number;
112
readonly mimeType?: string;
113
}
114
115
export interface IDiagnosticVariableEntryFilterData {
116
readonly owner?: string;
117
readonly problemMessage?: string;
118
readonly filterUri?: URI;
119
readonly filterSeverity?: MarkerSeverity;
120
readonly filterRange?: IRange;
121
}
122
123
124
125
export namespace IDiagnosticVariableEntryFilterData {
126
export const icon = Codicon.error;
127
128
export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData {
129
return {
130
filterUri: marker.resource,
131
owner: marker.owner,
132
problemMessage: marker.message,
133
filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn }
134
};
135
}
136
137
export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry {
138
return {
139
id: id(data),
140
name: label(data),
141
icon,
142
value: data,
143
kind: 'diagnostic',
144
...data,
145
};
146
}
147
148
export function id(data: IDiagnosticVariableEntryFilterData) {
149
return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber].join(':');
150
}
151
152
export function label(data: IDiagnosticVariableEntryFilterData) {
153
const enum TrimThreshold {
154
MaxChars = 30,
155
MaxSpaceLookback = 10,
156
}
157
if (data.problemMessage) {
158
if (data.problemMessage.length < TrimThreshold.MaxChars) {
159
return data.problemMessage;
160
}
161
162
// Trim the message, on a space if it would not lose too much
163
// data (MaxSpaceLookback) or just blindly otherwise.
164
const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars);
165
if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) {
166
return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…';
167
}
168
return data.problemMessage.substring(0, lastSpace) + '…';
169
}
170
let labelStr = localize('chat.attachment.problems.all', "All Problems");
171
if (data.filterUri) {
172
labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri));
173
}
174
175
return labelStr;
176
}
177
}
178
179
export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData {
180
readonly kind: 'diagnostic';
181
}
182
183
export interface IElementVariableEntry extends IBaseChatRequestVariableEntry {
184
readonly kind: 'element';
185
}
186
187
export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry {
188
readonly kind: 'promptFile';
189
readonly value: URI;
190
readonly isRoot: boolean;
191
readonly originLabel?: string;
192
readonly modelDescription: string;
193
readonly automaticallyAdded: boolean;
194
readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];
195
}
196
197
export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry {
198
readonly kind: 'promptText';
199
readonly value: string;
200
readonly settingId?: string;
201
readonly modelDescription: string;
202
readonly automaticallyAdded: boolean;
203
readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];
204
}
205
206
export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry {
207
readonly kind: 'scmHistoryItem';
208
readonly value: URI;
209
readonly historyItem: ISCMHistoryItem;
210
}
211
212
export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry
213
| ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry
214
| IChatRequestToolEntry | IChatRequestToolSetEntry
215
| IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry
216
| IPromptFileVariableEntry | IPromptTextVariableEntry | ISCMHistoryItemVariableEntry;
217
218
219
export namespace IChatRequestVariableEntry {
220
221
/**
222
* Returns URI of the passed variant entry. Return undefined if not found.
223
*/
224
export function toUri(entry: IChatRequestVariableEntry): URI | undefined {
225
return URI.isUri(entry.value)
226
? entry.value
227
: isLocation(entry.value)
228
? entry.value.uri
229
: undefined;
230
}
231
}
232
233
234
export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry {
235
return obj.kind === 'implicit';
236
}
237
238
export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry {
239
return obj.kind === 'paste';
240
}
241
242
export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry {
243
return obj.kind === 'image';
244
}
245
246
export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry {
247
return obj.kind === 'notebookOutput';
248
}
249
250
export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry {
251
return obj.kind === 'element';
252
}
253
254
export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry {
255
return obj.kind === 'diagnostic';
256
}
257
258
export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry {
259
return obj.kind === 'file';
260
}
261
262
export function isPromptFileVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptFileVariableEntry {
263
return obj.kind === 'promptFile';
264
}
265
266
export function isPromptTextVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptTextVariableEntry {
267
return obj.kind === 'promptText';
268
}
269
270
export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry {
271
const entry = obj as IChatRequestVariableEntry;
272
return typeof entry === 'object' &&
273
entry !== null &&
274
typeof entry.id === 'string' &&
275
typeof entry.name === 'string';
276
}
277
278
export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry {
279
return obj.kind === 'scmHistoryItem';
280
}
281
282
export enum PromptFileVariableKind {
283
Instruction = 'vscode.prompt.instructions.root',
284
InstructionReference = `vscode.prompt.instructions`,
285
PromptFile = 'vscode.prompt.file'
286
}
287
288
/**
289
* Utility to convert a {@link uri} to a chat variable entry.
290
* The `id` of the chat variable can be one of the following:
291
*
292
* - `vscode.prompt.instructions__<URI>`: for all non-root prompt instructions references
293
* - `vscode.prompt.instructions.root__<URI>`: for *root* prompt instructions references
294
* - `vscode.prompt.file__<URI>`: for prompt file references
295
*
296
* @param uri A resource URI that points to a prompt instructions file.
297
* @param kind The kind of the prompt file variable entry.
298
*/
299
export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind, originLabel?: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptFileVariableEntry {
300
// `id` for all `prompt files` starts with the well-defined part that the copilot extension(or other chatbot) can rely on
301
return {
302
id: `${kind}__${uri.toString()}`,
303
name: `prompt:${basename(uri)}`,
304
value: uri,
305
kind: 'promptFile',
306
modelDescription: 'Prompt instructions file',
307
isRoot: kind !== PromptFileVariableKind.InstructionReference,
308
originLabel,
309
toolReferences,
310
automaticallyAdded
311
};
312
}
313
314
export function toPromptTextVariableEntry(content: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptTextVariableEntry {
315
return {
316
id: `vscode.prompt.instructions.text`,
317
name: `prompt:instructionsList`,
318
value: content,
319
kind: 'promptText',
320
modelDescription: 'Prompt instructions list',
321
automaticallyAdded,
322
toolReferences
323
};
324
}
325
326
export function toFileVariableEntry(uri: URI, range?: IRange): IChatRequestFileEntry {
327
return {
328
kind: 'file',
329
value: range ? { uri, range } : uri,
330
id: uri.toString() + (range?.toString() ?? ''),
331
name: basename(uri),
332
};
333
}
334
335
export function toToolVariableEntry(entry: IToolData, range?: IOffsetRange): IChatRequestToolEntry {
336
return {
337
kind: 'tool',
338
id: entry.id,
339
icon: ThemeIcon.isThemeIcon(entry.icon) ? entry.icon : undefined,
340
name: entry.displayName,
341
value: undefined,
342
range
343
};
344
}
345
346
export function toToolSetVariableEntry(entry: ToolSet, range?: IOffsetRange): IChatRequestToolSetEntry {
347
return {
348
kind: 'toolset',
349
id: entry.id,
350
icon: entry.icon,
351
name: entry.referenceName,
352
value: Array.from(entry.getTools()).map(t => toToolVariableEntry(t)),
353
range
354
};
355
}
356
357
export class ChatRequestVariableSet {
358
private _ids = new Set<string>();
359
private _entries: IChatRequestVariableEntry[] = [];
360
361
constructor(entries?: IChatRequestVariableEntry[]) {
362
if (entries) {
363
this.add(...entries);
364
}
365
}
366
367
public add(...entry: IChatRequestVariableEntry[]): void {
368
for (const e of entry) {
369
if (!this._ids.has(e.id)) {
370
this._ids.add(e.id);
371
this._entries.push(e);
372
}
373
}
374
}
375
376
public insertFirst(entry: IChatRequestVariableEntry): void {
377
if (!this._ids.has(entry.id)) {
378
this._ids.add(entry.id);
379
this._entries.unshift(entry);
380
}
381
}
382
383
public remove(entry: IChatRequestVariableEntry): void {
384
this._ids.delete(entry.id);
385
this._entries = this._entries.filter(e => e.id !== entry.id);
386
}
387
388
public has(entry: IChatRequestVariableEntry): boolean {
389
return this._ids.has(entry.id);
390
}
391
392
public asArray(): IChatRequestVariableEntry[] {
393
return this._entries.slice(0); // return a copy
394
}
395
396
public get length(): number {
397
return this._entries.length;
398
}
399
}
400
401
402