Path: blob/main/src/vs/workbench/contrib/chat/common/attachments/chatVariableEntries.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Codicon } from '../../../../../base/common/codicons.js';6import { basename } from '../../../../../base/common/resources.js';7import { ThemeIcon } from '../../../../../base/common/themables.js';8import { URI } from '../../../../../base/common/uri.js';9import { IRange } from '../../../../../editor/common/core/range.js';10import { IOffsetRange } from '../../../../../editor/common/core/ranges/offsetRange.js';11import { isLocation, Location, SymbolKind } from '../../../../../editor/common/languages.js';12import { localize } from '../../../../../nls.js';13import { MarkerSeverity, IMarker } from '../../../../../platform/markers/common/markers.js';14import { ISCMHistoryItem } from '../../../scm/common/history.js';15import { IChatContentReference } from '../chatService/chatService.js';16import { IChatRequestVariableValue } from './chatVariables.js';17import { IToolData, ToolSet } from '../tools/languageModelToolsService.js';18import { decodeBase64, encodeBase64, VSBuffer } from '../../../../../base/common/buffer.js';19import { Mutable } from '../../../../../base/common/types.js';202122interface IBaseChatRequestVariableEntry {23readonly id: string;24readonly fullName?: string;25readonly icon?: ThemeIcon;26readonly name: string;27readonly modelDescription?: string;2829/**30* The offset-range in the prompt. This means this entry has been explicitly typed out31* by the user.32*/33readonly range?: IOffsetRange;34readonly value: IChatRequestVariableValue;35readonly references?: IChatContentReference[];3637omittedState?: OmittedState;38}3940export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry {41kind: 'generic';42}4344export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry {45kind: 'directory';46}4748export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry {49kind: 'file';50}5152export const enum OmittedState {53NotOmitted,54Partial,55Full,56}5758export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry {59readonly kind: 'tool';60}6162export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry {63readonly kind: 'toolset';64readonly value: IChatRequestToolEntry[];65}6667export type ChatRequestToolReferenceEntry = IChatRequestToolEntry | IChatRequestToolSetEntry;6869export interface StringChatContextValue {70value?: string;71name: string;72modelDescription?: string;73icon: ThemeIcon;74uri: URI;75}7677export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry {78readonly kind: 'implicit';79readonly isFile: true;80readonly value: URI | Location | StringChatContextValue | undefined;81readonly uri: URI | undefined;82readonly isSelection: boolean;83enabled: boolean;84}8586export interface IChatRequestStringVariableEntry extends IBaseChatRequestVariableEntry {87readonly kind: 'string';88readonly value: string | undefined;89readonly modelDescription?: string;90readonly icon: ThemeIcon;91readonly uri: URI;92}9394export interface IChatRequestWorkspaceVariableEntry extends IBaseChatRequestVariableEntry {95readonly kind: 'workspace';96readonly value: string;97readonly modelDescription?: string;98}99100101export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry {102readonly kind: 'paste';103readonly code: string;104readonly language: string;105readonly pastedLines: string;106107// This is only used for old serialized data and should be removed once we no longer support it108readonly fileName: string;109110// This is only undefined on old serialized data111readonly copiedFrom: {112readonly uri: URI;113readonly range: IRange;114} | undefined;115}116117export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry {118readonly kind: 'symbol';119readonly value: Location;120readonly symbolKind: SymbolKind;121}122123export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry {124readonly kind: 'command';125}126127export interface IImageVariableEntry extends IBaseChatRequestVariableEntry {128readonly kind: 'image';129readonly isPasted?: boolean;130readonly isURL?: boolean;131readonly mimeType?: string;132}133134export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry {135readonly kind: 'notebookOutput';136readonly outputIndex?: number;137readonly mimeType?: string;138}139140export interface IDiagnosticVariableEntryFilterData {141readonly owner?: string;142readonly problemMessage?: string;143readonly filterUri?: URI;144readonly filterSeverity?: MarkerSeverity;145readonly filterRange?: IRange;146}147148149150export namespace IDiagnosticVariableEntryFilterData {151export const icon = Codicon.error;152153export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData {154return {155filterUri: marker.resource,156owner: marker.owner,157problemMessage: marker.message,158filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn }159};160}161162export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry {163return {164id: id(data),165name: label(data),166icon,167value: data,168kind: 'diagnostic',169...data,170};171}172173export function id(data: IDiagnosticVariableEntryFilterData) {174return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber, data.filterRange?.startColumn].join(':');175}176177export function label(data: IDiagnosticVariableEntryFilterData) {178const enum TrimThreshold {179MaxChars = 30,180MaxSpaceLookback = 10,181}182if (data.problemMessage) {183if (data.problemMessage.length < TrimThreshold.MaxChars) {184return data.problemMessage;185}186187// Trim the message, on a space if it would not lose too much188// data (MaxSpaceLookback) or just blindly otherwise.189const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars);190if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) {191return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…';192}193return data.problemMessage.substring(0, lastSpace) + '…';194}195let labelStr = localize('chat.attachment.problems.all', "All Problems");196if (data.filterUri) {197labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri));198}199200return labelStr;201}202}203204export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData {205readonly kind: 'diagnostic';206}207208export interface IElementVariableEntry extends IBaseChatRequestVariableEntry {209readonly kind: 'element';210}211212export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry {213readonly kind: 'promptFile';214readonly value: URI;215readonly isRoot: boolean;216readonly originLabel?: string;217readonly modelDescription: string;218readonly automaticallyAdded: boolean;219readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];220}221222export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry {223readonly kind: 'promptText';224readonly value: string;225readonly settingId?: string;226readonly modelDescription: string;227readonly automaticallyAdded: boolean;228readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];229}230231export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry {232readonly kind: 'scmHistoryItem';233readonly value: URI;234readonly historyItem: ISCMHistoryItem;235}236237export interface ISCMHistoryItemChangeVariableEntry extends IBaseChatRequestVariableEntry {238readonly kind: 'scmHistoryItemChange';239readonly value: URI;240readonly historyItem: ISCMHistoryItem;241}242243export interface ISCMHistoryItemChangeRangeVariableEntry extends IBaseChatRequestVariableEntry {244readonly kind: 'scmHistoryItemChangeRange';245readonly value: URI;246readonly historyItemChangeStart: {247readonly uri: URI;248readonly historyItem: ISCMHistoryItem;249};250readonly historyItemChangeEnd: {251readonly uri: URI;252readonly historyItem: ISCMHistoryItem;253};254}255256export interface ITerminalVariableEntry extends IBaseChatRequestVariableEntry {257readonly kind: 'terminalCommand';258readonly value: string;259readonly resource: URI;260readonly command: string;261readonly output?: string;262readonly exitCode?: number;263}264265export interface IDebugVariableEntry extends IBaseChatRequestVariableEntry {266readonly kind: 'debugVariable';267readonly value: string;268readonly expression: string;269readonly type?: string;270}271272export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry273| ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry274| IChatRequestToolEntry | IChatRequestToolSetEntry275| IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry276| IPromptFileVariableEntry | IPromptTextVariableEntry277| ISCMHistoryItemVariableEntry | ISCMHistoryItemChangeVariableEntry | ISCMHistoryItemChangeRangeVariableEntry | ITerminalVariableEntry278| IChatRequestStringVariableEntry | IChatRequestWorkspaceVariableEntry | IDebugVariableEntry;279280export namespace IChatRequestVariableEntry {281282/**283* Returns URI of the passed variant entry. Return undefined if not found.284*/285export function toUri(entry: IChatRequestVariableEntry): URI | undefined {286return URI.isUri(entry.value)287? entry.value288: isLocation(entry.value)289? entry.value.uri290: undefined;291}292293export function toExport(v: IChatRequestVariableEntry): IChatRequestVariableEntry {294if (v.value instanceof Uint8Array) {295// 'dup' here is needed otherwise TS complains about the narrowed `value` in a spread operation296const dup: Mutable<IChatRequestVariableEntry> = { ...v };297dup.value = { $base64: encodeBase64(VSBuffer.wrap(v.value)) };298return dup;299}300301return v;302}303304export function fromExport(v: IChatRequestVariableEntry): IChatRequestVariableEntry {305// Old variables format306// eslint-disable-next-line local/code-no-in-operator307if (v && 'values' in v && Array.isArray(v.values)) {308return {309kind: 'generic',310id: v.id ?? '',311name: v.name,312value: v.values[0]?.value,313range: v.range,314modelDescription: v.modelDescription,315references: v.references316};317} else {318// eslint-disable-next-line local/code-no-in-operator319if (v.value && typeof v.value === 'object' && '$base64' in v.value && typeof v.value.$base64 === 'string') {320// 'dup' here is needed otherwise TS complains about the narrowed `value` in a spread operation321const dup: Mutable<IChatRequestVariableEntry> = { ...v };322dup.value = decodeBase64(v.value.$base64).buffer;323return dup;324}325326return v;327}328}329}330331332export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry {333return obj.kind === 'implicit';334}335336export function isStringVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestStringVariableEntry {337return obj.kind === 'string';338}339340export function isTerminalVariableEntry(obj: IChatRequestVariableEntry): obj is ITerminalVariableEntry {341return obj.kind === 'terminalCommand';342}343344export function isDebugVariableEntry(obj: IChatRequestVariableEntry): obj is IDebugVariableEntry {345return obj.kind === 'debugVariable';346}347348export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry {349return obj.kind === 'paste';350}351352export function isWorkspaceVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestWorkspaceVariableEntry {353return obj.kind === 'workspace';354}355356export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry {357return obj.kind === 'image';358}359360export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry {361return obj.kind === 'notebookOutput';362}363364export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry {365return obj.kind === 'element';366}367368export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry {369return obj.kind === 'diagnostic';370}371372export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry {373return obj.kind === 'file';374}375376export function isPromptFileVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptFileVariableEntry {377return obj.kind === 'promptFile';378}379380export function isPromptTextVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptTextVariableEntry {381return obj.kind === 'promptText';382}383384export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry {385const entry = obj as IChatRequestVariableEntry;386return typeof entry === 'object' &&387entry !== null &&388typeof entry.id === 'string' &&389typeof entry.name === 'string';390}391392export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry {393return obj.kind === 'scmHistoryItem';394}395396export function isSCMHistoryItemChangeVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemChangeVariableEntry {397return obj.kind === 'scmHistoryItemChange';398}399400export function isSCMHistoryItemChangeRangeVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemChangeRangeVariableEntry {401return obj.kind === 'scmHistoryItemChangeRange';402}403404export function isStringImplicitContextValue(value: unknown): value is StringChatContextValue {405const asStringImplicitContextValue = value as Partial<StringChatContextValue>;406return (407typeof asStringImplicitContextValue === 'object' &&408asStringImplicitContextValue !== null &&409(typeof asStringImplicitContextValue.value === 'string' || typeof asStringImplicitContextValue.value === 'undefined') &&410typeof asStringImplicitContextValue.name === 'string' &&411ThemeIcon.isThemeIcon(asStringImplicitContextValue.icon) &&412URI.isUri(asStringImplicitContextValue.uri)413);414}415416export enum PromptFileVariableKind {417Instruction = 'vscode.prompt.instructions.root',418InstructionReference = `vscode.prompt.instructions`,419PromptFile = 'vscode.prompt.file'420}421422/**423* Utility to convert a {@link uri} to a chat variable entry.424* The `id` of the chat variable can be one of the following:425*426* - `vscode.prompt.instructions__<URI>`: for all non-root prompt instructions references427* - `vscode.prompt.instructions.root__<URI>`: for *root* prompt instructions references428* - `vscode.prompt.file__<URI>`: for prompt file references429*430* @param uri A resource URI that points to a prompt instructions file.431* @param kind The kind of the prompt file variable entry.432*/433export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind, originLabel?: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptFileVariableEntry {434// `id` for all `prompt files` starts with the well-defined part that the copilot extension(or other chatbot) can rely on435return {436id: `${kind}__${uri.toString()}`,437name: `prompt:${basename(uri)}`,438value: uri,439kind: 'promptFile',440modelDescription: 'Prompt instructions file',441isRoot: kind !== PromptFileVariableKind.InstructionReference,442originLabel,443toolReferences,444automaticallyAdded445};446}447448export function toPromptTextVariableEntry(content: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptTextVariableEntry {449return {450id: `vscode.prompt.instructions.text`,451name: `prompt:instructionsList`,452value: content,453kind: 'promptText',454modelDescription: 'Prompt instructions list',455automaticallyAdded,456toolReferences457};458}459460export function toFileVariableEntry(uri: URI, range?: IRange): IChatRequestFileEntry {461return {462kind: 'file',463value: range ? { uri, range } : uri,464id: uri.toString() + (range?.toString() ?? ''),465name: basename(uri),466};467}468469export function toToolVariableEntry(entry: IToolData, range?: IOffsetRange): IChatRequestToolEntry {470return {471kind: 'tool',472id: entry.id,473icon: ThemeIcon.isThemeIcon(entry.icon) ? entry.icon : undefined,474name: entry.displayName,475value: undefined,476range477};478}479480export function toToolSetVariableEntry(entry: ToolSet, range?: IOffsetRange): IChatRequestToolSetEntry {481return {482kind: 'toolset',483id: entry.id,484icon: entry.icon,485name: entry.referenceName,486value: Array.from(entry.getTools()).map(t => toToolVariableEntry(t)),487range488};489}490491export class ChatRequestVariableSet {492private _ids = new Set<string>();493private _entries: IChatRequestVariableEntry[] = [];494495constructor(entries?: IChatRequestVariableEntry[]) {496if (entries) {497this.add(...entries);498}499}500501public add(...entry: IChatRequestVariableEntry[]): void {502for (const e of entry) {503if (!this._ids.has(e.id)) {504this._ids.add(e.id);505this._entries.push(e);506}507}508}509510public insertFirst(entry: IChatRequestVariableEntry): void {511if (!this._ids.has(entry.id)) {512this._ids.add(entry.id);513this._entries.unshift(entry);514}515}516517public remove(entry: IChatRequestVariableEntry): void {518this._ids.delete(entry.id);519this._entries = this._entries.filter(e => e.id !== entry.id);520}521522public has(entry: IChatRequestVariableEntry): boolean {523return this._ids.has(entry.id);524}525526public asArray(): IChatRequestVariableEntry[] {527return this._entries.slice(0); // return a copy528}529530public get length(): number {531return this._entries.length;532}533}534535536