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