Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/attachments/chatVariableEntries.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 { 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/chatService.js';
17
import { IChatRequestVariableValue } from './chatVariables.js';
18
import { IToolData, ToolSet } from '../tools/languageModelToolsService.js';
19
import { decodeBase64, encodeBase64, VSBuffer } from '../../../../../base/common/buffer.js';
20
import { Mutable } from '../../../../../base/common/types.js';
21
22
23
interface IBaseChatRequestVariableEntry {
24
readonly id: string;
25
readonly fullName?: string;
26
readonly icon?: ThemeIcon;
27
readonly name: string;
28
readonly modelDescription?: string;
29
30
/**
31
* The offset-range in the prompt. This means this entry has been explicitly typed out
32
* by the user.
33
*/
34
readonly range?: IOffsetRange;
35
readonly value: IChatRequestVariableValue;
36
readonly references?: IChatContentReference[];
37
38
omittedState?: OmittedState;
39
}
40
41
export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry {
42
kind: 'generic';
43
}
44
45
export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry {
46
kind: 'directory';
47
}
48
49
export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry {
50
kind: 'file';
51
}
52
53
export const enum OmittedState {
54
NotOmitted,
55
Partial,
56
Full,
57
}
58
59
export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry {
60
readonly kind: 'tool';
61
}
62
63
export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry {
64
readonly kind: 'toolset';
65
readonly value: IChatRequestToolEntry[];
66
}
67
68
export type ChatRequestToolReferenceEntry = IChatRequestToolEntry | IChatRequestToolSetEntry;
69
70
export interface StringChatContextValue {
71
value?: string;
72
name: string;
73
modelDescription?: string;
74
icon: ThemeIcon;
75
uri: URI;
76
}
77
78
export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry {
79
readonly kind: 'implicit';
80
readonly isFile: true;
81
readonly value: URI | Location | StringChatContextValue | undefined;
82
readonly uri: URI | undefined;
83
readonly isSelection: boolean;
84
enabled: boolean;
85
}
86
87
export interface IChatRequestStringVariableEntry extends IBaseChatRequestVariableEntry {
88
readonly kind: 'string';
89
readonly value: string | undefined;
90
readonly modelDescription?: string;
91
readonly icon: ThemeIcon;
92
readonly uri: URI;
93
}
94
95
export interface IChatRequestWorkspaceVariableEntry extends IBaseChatRequestVariableEntry {
96
readonly kind: 'workspace';
97
readonly value: string;
98
readonly modelDescription?: string;
99
}
100
101
102
export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry {
103
readonly kind: 'paste';
104
readonly code: string;
105
readonly language: string;
106
readonly pastedLines: string;
107
108
// This is only used for old serialized data and should be removed once we no longer support it
109
readonly fileName: string;
110
111
// This is only undefined on old serialized data
112
readonly copiedFrom: {
113
readonly uri: URI;
114
readonly range: IRange;
115
} | undefined;
116
}
117
118
export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry {
119
readonly kind: 'symbol';
120
readonly value: Location;
121
readonly symbolKind: SymbolKind;
122
}
123
124
export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry {
125
readonly kind: 'command';
126
}
127
128
export interface IImageVariableEntry extends IBaseChatRequestVariableEntry {
129
readonly kind: 'image';
130
readonly isPasted?: boolean;
131
readonly isURL?: boolean;
132
readonly mimeType?: string;
133
}
134
135
export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry {
136
readonly kind: 'notebookOutput';
137
readonly outputIndex?: number;
138
readonly mimeType?: string;
139
}
140
141
export interface IDiagnosticVariableEntryFilterData {
142
readonly owner?: string;
143
readonly problemMessage?: string;
144
readonly filterUri?: URI;
145
readonly filterSeverity?: MarkerSeverity;
146
readonly filterRange?: IRange;
147
}
148
149
150
151
export namespace IDiagnosticVariableEntryFilterData {
152
export const icon = Codicon.error;
153
154
export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData {
155
return {
156
filterUri: marker.resource,
157
owner: marker.owner,
158
problemMessage: marker.message,
159
filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn }
160
};
161
}
162
163
export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry {
164
return {
165
id: id(data),
166
name: label(data),
167
icon,
168
value: data,
169
kind: 'diagnostic',
170
...data,
171
};
172
}
173
174
export function id(data: IDiagnosticVariableEntryFilterData) {
175
return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber, data.filterRange?.startColumn].join(':');
176
}
177
178
export function label(data: IDiagnosticVariableEntryFilterData) {
179
const enum TrimThreshold {
180
MaxChars = 30,
181
MaxSpaceLookback = 10,
182
}
183
if (data.problemMessage) {
184
if (data.problemMessage.length < TrimThreshold.MaxChars) {
185
return data.problemMessage;
186
}
187
188
// Trim the message, on a space if it would not lose too much
189
// data (MaxSpaceLookback) or just blindly otherwise.
190
const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars);
191
if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) {
192
return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…';
193
}
194
return data.problemMessage.substring(0, lastSpace) + '…';
195
}
196
let labelStr = localize('chat.attachment.problems.all', "All Problems");
197
if (data.filterUri) {
198
labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri));
199
}
200
201
return labelStr;
202
}
203
}
204
205
export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData {
206
readonly kind: 'diagnostic';
207
}
208
209
export interface IElementVariableEntry extends IBaseChatRequestVariableEntry {
210
readonly kind: 'element';
211
}
212
213
export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry {
214
readonly kind: 'promptFile';
215
readonly value: URI;
216
readonly isRoot: boolean;
217
readonly originLabel?: string;
218
readonly modelDescription: string;
219
readonly automaticallyAdded: boolean;
220
readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];
221
}
222
223
export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry {
224
readonly kind: 'promptText';
225
readonly value: string;
226
readonly settingId?: string;
227
readonly modelDescription: string;
228
readonly automaticallyAdded: boolean;
229
readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];
230
}
231
232
export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry {
233
readonly kind: 'scmHistoryItem';
234
readonly value: URI;
235
readonly historyItem: ISCMHistoryItem;
236
}
237
238
export interface ISCMHistoryItemChangeVariableEntry extends IBaseChatRequestVariableEntry {
239
readonly kind: 'scmHistoryItemChange';
240
readonly value: URI;
241
readonly historyItem: ISCMHistoryItem;
242
}
243
244
export interface ISCMHistoryItemChangeRangeVariableEntry extends IBaseChatRequestVariableEntry {
245
readonly kind: 'scmHistoryItemChangeRange';
246
readonly value: URI;
247
readonly historyItemChangeStart: {
248
readonly uri: URI;
249
readonly historyItem: ISCMHistoryItem;
250
};
251
readonly historyItemChangeEnd: {
252
readonly uri: URI;
253
readonly historyItem: ISCMHistoryItem;
254
};
255
}
256
257
export interface ITerminalVariableEntry extends IBaseChatRequestVariableEntry {
258
readonly kind: 'terminalCommand';
259
readonly value: string;
260
readonly resource: URI;
261
readonly command: string;
262
readonly output?: string;
263
readonly exitCode?: number;
264
}
265
266
export interface IDebugVariableEntry extends IBaseChatRequestVariableEntry {
267
readonly kind: 'debugVariable';
268
readonly value: string;
269
readonly expression: string;
270
readonly type?: string;
271
}
272
273
export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry
274
| ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry
275
| IChatRequestToolEntry | IChatRequestToolSetEntry
276
| IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry
277
| IPromptFileVariableEntry | IPromptTextVariableEntry
278
| ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry | ITerminalVariableEntry
279
| IChatRequestStringVariableEntry | IChatRequestWorkspaceVariableEntry | IDebugVariableEntry;
280
281
export namespace IChatRequestVariableEntry {
282
283
/**
284
* Returns URI of the passed variant entry. Return undefined if not found.
285
*/
286
export function toUri(entry: IChatRequestVariableEntry): URI | undefined {
287
return URI.isUri(entry.value)
288
? entry.value
289
: isLocation(entry.value)
290
? entry.value.uri
291
: undefined;
292
}
293
294
export function toExport(v: IChatRequestVariableEntry): IChatRequestVariableEntry {
295
if (v.value instanceof Uint8Array) {
296
// 'dup' here is needed otherwise TS complains about the narrowed `value` in a spread operation
297
const dup: Mutable<IChatRequestVariableEntry> = { ...v };
298
dup.value = { $base64: encodeBase64(VSBuffer.wrap(v.value)) };
299
return dup;
300
}
301
302
return v;
303
}
304
305
export function fromExport(v: IChatRequestVariableEntry): IChatRequestVariableEntry {
306
// Old variables format
307
// eslint-disable-next-line local/code-no-in-operator
308
if (v && 'values' in v && Array.isArray(v.values)) {
309
return {
310
kind: 'generic',
311
id: v.id ?? '',
312
name: v.name,
313
value: v.values[0]?.value,
314
range: v.range,
315
modelDescription: v.modelDescription,
316
references: v.references
317
};
318
} else {
319
// eslint-disable-next-line local/code-no-in-operator
320
if (v.value && typeof v.value === 'object' && '$base64' in v.value && typeof v.value.$base64 === 'string') {
321
// 'dup' here is needed otherwise TS complains about the narrowed `value` in a spread operation
322
const dup: Mutable<IChatRequestVariableEntry> = { ...v };
323
dup.value = decodeBase64(v.value.$base64).buffer;
324
return dup;
325
}
326
327
return v;
328
}
329
}
330
}
331
332
333
export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry {
334
return obj.kind === 'implicit';
335
}
336
337
export function isStringVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestStringVariableEntry {
338
return obj.kind === 'string';
339
}
340
341
export function isTerminalVariableEntry(obj: IChatRequestVariableEntry): obj is ITerminalVariableEntry {
342
return obj.kind === 'terminalCommand';
343
}
344
345
export function isDebugVariableEntry(obj: IChatRequestVariableEntry): obj is IDebugVariableEntry {
346
return obj.kind === 'debugVariable';
347
}
348
349
export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry {
350
return obj.kind === 'paste';
351
}
352
353
export function isWorkspaceVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestWorkspaceVariableEntry {
354
return obj.kind === 'workspace';
355
}
356
357
export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry {
358
return obj.kind === 'image';
359
}
360
361
export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry {
362
return obj.kind === 'notebookOutput';
363
}
364
365
export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry {
366
return obj.kind === 'element';
367
}
368
369
export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry {
370
return obj.kind === 'diagnostic';
371
}
372
373
export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry {
374
return obj.kind === 'file';
375
}
376
377
export function isPromptFileVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptFileVariableEntry {
378
return obj.kind === 'promptFile';
379
}
380
381
export function isPromptTextVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptTextVariableEntry {
382
return obj.kind === 'promptText';
383
}
384
385
export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry {
386
const entry = obj as IChatRequestVariableEntry;
387
return typeof entry === 'object' &&
388
entry !== null &&
389
typeof entry.id === 'string' &&
390
typeof entry.name === 'string';
391
}
392
393
export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry {
394
return obj.kind === 'scmHistoryItem';
395
}
396
397
export function isSCMHistoryItemChangeVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemChangeVariableEntry {
398
return obj.kind === 'scmHistoryItemChange';
399
}
400
401
export function isSCMHistoryItemChangeRangeVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemChangeRangeVariableEntry {
402
return obj.kind === 'scmHistoryItemChangeRange';
403
}
404
405
export function isStringImplicitContextValue(value: unknown): value is StringChatContextValue {
406
const asStringImplicitContextValue = value as Partial<StringChatContextValue>;
407
return (
408
typeof asStringImplicitContextValue === 'object' &&
409
asStringImplicitContextValue !== null &&
410
(typeof asStringImplicitContextValue.value === 'string' || typeof asStringImplicitContextValue.value === 'undefined') &&
411
typeof asStringImplicitContextValue.name === 'string' &&
412
ThemeIcon.isThemeIcon(asStringImplicitContextValue.icon) &&
413
URI.isUri(asStringImplicitContextValue.uri)
414
);
415
}
416
417
export enum PromptFileVariableKind {
418
Instruction = 'vscode.prompt.instructions.root',
419
InstructionReference = `vscode.prompt.instructions`,
420
PromptFile = 'vscode.prompt.file'
421
}
422
423
/**
424
* Utility to convert a {@link uri} to a chat variable entry.
425
* The `id` of the chat variable can be one of the following:
426
*
427
* - `vscode.prompt.instructions__<URI>`: for all non-root prompt instructions references
428
* - `vscode.prompt.instructions.root__<URI>`: for *root* prompt instructions references
429
* - `vscode.prompt.file__<URI>`: for prompt file references
430
*
431
* @param uri A resource URI that points to a prompt instructions file.
432
* @param kind The kind of the prompt file variable entry.
433
*/
434
export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind, originLabel?: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptFileVariableEntry {
435
// `id` for all `prompt files` starts with the well-defined part that the copilot extension(or other chatbot) can rely on
436
return {
437
id: `${kind}__${uri.toString()}`,
438
name: `prompt:${basename(uri)}`,
439
value: uri,
440
kind: 'promptFile',
441
modelDescription: 'Prompt instructions file',
442
isRoot: kind !== PromptFileVariableKind.InstructionReference,
443
originLabel,
444
toolReferences,
445
automaticallyAdded
446
};
447
}
448
449
export function toPromptTextVariableEntry(content: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptTextVariableEntry {
450
return {
451
id: `vscode.prompt.instructions.text`,
452
name: `prompt:instructionsList`,
453
value: content,
454
kind: 'promptText',
455
modelDescription: 'Prompt instructions list',
456
automaticallyAdded,
457
toolReferences
458
};
459
}
460
461
export function toFileVariableEntry(uri: URI, range?: IRange): IChatRequestFileEntry {
462
return {
463
kind: 'file',
464
value: range ? { uri, range } : uri,
465
id: uri.toString() + (range?.toString() ?? ''),
466
name: basename(uri),
467
};
468
}
469
470
export function toToolVariableEntry(entry: IToolData, range?: IOffsetRange): IChatRequestToolEntry {
471
return {
472
kind: 'tool',
473
id: entry.id,
474
icon: ThemeIcon.isThemeIcon(entry.icon) ? entry.icon : undefined,
475
name: entry.displayName,
476
value: undefined,
477
range
478
};
479
}
480
481
export function toToolSetVariableEntry(entry: ToolSet, range?: IOffsetRange): IChatRequestToolSetEntry {
482
return {
483
kind: 'toolset',
484
id: entry.id,
485
icon: entry.icon,
486
name: entry.referenceName,
487
value: Array.from(entry.getTools()).map(t => toToolVariableEntry(t)),
488
range
489
};
490
}
491
492
export class ChatRequestVariableSet {
493
private _ids = new Set<string>();
494
private _entries: IChatRequestVariableEntry[] = [];
495
496
constructor(entries?: IChatRequestVariableEntry[]) {
497
if (entries) {
498
this.add(...entries);
499
}
500
}
501
502
public add(...entry: IChatRequestVariableEntry[]): void {
503
for (const e of entry) {
504
if (!this._ids.has(e.id)) {
505
this._ids.add(e.id);
506
this._entries.push(e);
507
}
508
}
509
}
510
511
public insertFirst(entry: IChatRequestVariableEntry): void {
512
if (!this._ids.has(entry.id)) {
513
this._ids.add(entry.id);
514
this._entries.unshift(entry);
515
}
516
}
517
518
public remove(entry: IChatRequestVariableEntry): void {
519
this._ids.delete(entry.id);
520
this._entries = this._entries.filter(e => e.id !== entry.id);
521
}
522
523
public has(entry: IChatRequestVariableEntry): boolean {
524
return this._ids.has(entry.id);
525
}
526
527
public asArray(): IChatRequestVariableEntry[] {
528
return this._entries.slice(0); // return a copy
529
}
530
531
public get length(): number {
532
return this._entries.length;
533
}
534
}
535
536