Path: blob/main/src/vs/workbench/contrib/chat/common/chatVariableEntries.ts
3296 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.js';16import { IChatRequestVariableValue } from './chatVariables.js';17import { IToolData, ToolSet } from './languageModelToolsService.js';181920interface IBaseChatRequestVariableEntry {21readonly id: string;22readonly fullName?: string;23readonly icon?: ThemeIcon;24readonly name: string;25readonly modelDescription?: string;2627/**28* The offset-range in the prompt. This means this entry has been explicitly typed out29* by the user.30*/31readonly range?: IOffsetRange;32readonly value: IChatRequestVariableValue;33readonly references?: IChatContentReference[];3435omittedState?: OmittedState;36}3738export interface IGenericChatRequestVariableEntry extends IBaseChatRequestVariableEntry {39kind: 'generic';40}4142export interface IChatRequestDirectoryEntry extends IBaseChatRequestVariableEntry {43kind: 'directory';44}4546export interface IChatRequestFileEntry extends IBaseChatRequestVariableEntry {47kind: 'file';48}4950export const enum OmittedState {51NotOmitted,52Partial,53Full,54}5556export interface IChatRequestToolEntry extends IBaseChatRequestVariableEntry {57readonly kind: 'tool';58}5960export interface IChatRequestToolSetEntry extends IBaseChatRequestVariableEntry {61readonly kind: 'toolset';62readonly value: IChatRequestToolEntry[];63}6465export type ChatRequestToolReferenceEntry = IChatRequestToolEntry | IChatRequestToolSetEntry;6667export interface IChatRequestImplicitVariableEntry extends IBaseChatRequestVariableEntry {68readonly kind: 'implicit';69readonly isFile: true;70readonly value: URI | Location | undefined;71readonly isSelection: boolean;72enabled: boolean;73}7475export interface IChatRequestPasteVariableEntry extends IBaseChatRequestVariableEntry {76readonly kind: 'paste';77readonly code: string;78readonly language: string;79readonly pastedLines: string;8081// This is only used for old serialized data and should be removed once we no longer support it82readonly fileName: string;8384// This is only undefined on old serialized data85readonly copiedFrom: {86readonly uri: URI;87readonly range: IRange;88} | undefined;89}9091export interface ISymbolVariableEntry extends IBaseChatRequestVariableEntry {92readonly kind: 'symbol';93readonly value: Location;94readonly symbolKind: SymbolKind;95}9697export interface ICommandResultVariableEntry extends IBaseChatRequestVariableEntry {98readonly kind: 'command';99}100101export interface IImageVariableEntry extends IBaseChatRequestVariableEntry {102readonly kind: 'image';103readonly isPasted?: boolean;104readonly isURL?: boolean;105readonly mimeType?: string;106}107108export interface INotebookOutputVariableEntry extends IBaseChatRequestVariableEntry {109readonly kind: 'notebookOutput';110readonly outputIndex?: number;111readonly mimeType?: string;112}113114export interface IDiagnosticVariableEntryFilterData {115readonly owner?: string;116readonly problemMessage?: string;117readonly filterUri?: URI;118readonly filterSeverity?: MarkerSeverity;119readonly filterRange?: IRange;120}121122123124export namespace IDiagnosticVariableEntryFilterData {125export const icon = Codicon.error;126127export function fromMarker(marker: IMarker): IDiagnosticVariableEntryFilterData {128return {129filterUri: marker.resource,130owner: marker.owner,131problemMessage: marker.message,132filterRange: { startLineNumber: marker.startLineNumber, endLineNumber: marker.endLineNumber, startColumn: marker.startColumn, endColumn: marker.endColumn }133};134}135136export function toEntry(data: IDiagnosticVariableEntryFilterData): IDiagnosticVariableEntry {137return {138id: id(data),139name: label(data),140icon,141value: data,142kind: 'diagnostic',143...data,144};145}146147export function id(data: IDiagnosticVariableEntryFilterData) {148return [data.filterUri, data.owner, data.filterSeverity, data.filterRange?.startLineNumber].join(':');149}150151export function label(data: IDiagnosticVariableEntryFilterData) {152const enum TrimThreshold {153MaxChars = 30,154MaxSpaceLookback = 10,155}156if (data.problemMessage) {157if (data.problemMessage.length < TrimThreshold.MaxChars) {158return data.problemMessage;159}160161// Trim the message, on a space if it would not lose too much162// data (MaxSpaceLookback) or just blindly otherwise.163const lastSpace = data.problemMessage.lastIndexOf(' ', TrimThreshold.MaxChars);164if (lastSpace === -1 || lastSpace + TrimThreshold.MaxSpaceLookback < TrimThreshold.MaxChars) {165return data.problemMessage.substring(0, TrimThreshold.MaxChars) + '…';166}167return data.problemMessage.substring(0, lastSpace) + '…';168}169let labelStr = localize('chat.attachment.problems.all', "All Problems");170if (data.filterUri) {171labelStr = localize('chat.attachment.problems.inFile', "Problems in {0}", basename(data.filterUri));172}173174return labelStr;175}176}177178export interface IDiagnosticVariableEntry extends IBaseChatRequestVariableEntry, IDiagnosticVariableEntryFilterData {179readonly kind: 'diagnostic';180}181182export interface IElementVariableEntry extends IBaseChatRequestVariableEntry {183readonly kind: 'element';184}185186export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry {187readonly kind: 'promptFile';188readonly value: URI;189readonly isRoot: boolean;190readonly originLabel?: string;191readonly modelDescription: string;192readonly automaticallyAdded: boolean;193readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];194}195196export interface IPromptTextVariableEntry extends IBaseChatRequestVariableEntry {197readonly kind: 'promptText';198readonly value: string;199readonly settingId?: string;200readonly modelDescription: string;201readonly automaticallyAdded: boolean;202readonly toolReferences?: readonly ChatRequestToolReferenceEntry[];203}204205export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry {206readonly kind: 'scmHistoryItem';207readonly value: URI;208readonly historyItem: ISCMHistoryItem;209}210211export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry212| ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry213| IChatRequestToolEntry | IChatRequestToolSetEntry214| IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry215| IPromptFileVariableEntry | IPromptTextVariableEntry | ISCMHistoryItemVariableEntry;216217218export namespace IChatRequestVariableEntry {219220/**221* Returns URI of the passed variant entry. Return undefined if not found.222*/223export function toUri(entry: IChatRequestVariableEntry): URI | undefined {224return URI.isUri(entry.value)225? entry.value226: isLocation(entry.value)227? entry.value.uri228: undefined;229}230}231232233export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry {234return obj.kind === 'implicit';235}236237export function isPasteVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestPasteVariableEntry {238return obj.kind === 'paste';239}240241export function isImageVariableEntry(obj: IChatRequestVariableEntry): obj is IImageVariableEntry {242return obj.kind === 'image';243}244245export function isNotebookOutputVariableEntry(obj: IChatRequestVariableEntry): obj is INotebookOutputVariableEntry {246return obj.kind === 'notebookOutput';247}248249export function isElementVariableEntry(obj: IChatRequestVariableEntry): obj is IElementVariableEntry {250return obj.kind === 'element';251}252253export function isDiagnosticsVariableEntry(obj: IChatRequestVariableEntry): obj is IDiagnosticVariableEntry {254return obj.kind === 'diagnostic';255}256257export function isChatRequestFileEntry(obj: IChatRequestVariableEntry): obj is IChatRequestFileEntry {258return obj.kind === 'file';259}260261export function isPromptFileVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptFileVariableEntry {262return obj.kind === 'promptFile';263}264265export function isPromptTextVariableEntry(obj: IChatRequestVariableEntry): obj is IPromptTextVariableEntry {266return obj.kind === 'promptText';267}268269export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVariableEntry {270const entry = obj as IChatRequestVariableEntry;271return typeof entry === 'object' &&272entry !== null &&273typeof entry.id === 'string' &&274typeof entry.name === 'string';275}276277export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry {278return obj.kind === 'scmHistoryItem';279}280281export enum PromptFileVariableKind {282Instruction = 'vscode.prompt.instructions.root',283InstructionReference = `vscode.prompt.instructions`,284PromptFile = 'vscode.prompt.file'285}286287/**288* Utility to convert a {@link uri} to a chat variable entry.289* The `id` of the chat variable can be one of the following:290*291* - `vscode.prompt.instructions__<URI>`: for all non-root prompt instructions references292* - `vscode.prompt.instructions.root__<URI>`: for *root* prompt instructions references293* - `vscode.prompt.file__<URI>`: for prompt file references294*295* @param uri A resource URI that points to a prompt instructions file.296* @param kind The kind of the prompt file variable entry.297*/298export function toPromptFileVariableEntry(uri: URI, kind: PromptFileVariableKind, originLabel?: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptFileVariableEntry {299// `id` for all `prompt files` starts with the well-defined part that the copilot extension(or other chatbot) can rely on300return {301id: `${kind}__${uri.toString()}`,302name: `prompt:${basename(uri)}`,303value: uri,304kind: 'promptFile',305modelDescription: 'Prompt instructions file',306isRoot: kind !== PromptFileVariableKind.InstructionReference,307originLabel,308toolReferences,309automaticallyAdded310};311}312313export function toPromptTextVariableEntry(content: string, automaticallyAdded = false, toolReferences?: ChatRequestToolReferenceEntry[]): IPromptTextVariableEntry {314return {315id: `vscode.prompt.instructions.text`,316name: `prompt:instructionsList`,317value: content,318kind: 'promptText',319modelDescription: 'Prompt instructions list',320automaticallyAdded,321toolReferences322};323}324325export function toFileVariableEntry(uri: URI, range?: IRange): IChatRequestFileEntry {326return {327kind: 'file',328value: range ? { uri, range } : uri,329id: uri.toString() + (range?.toString() ?? ''),330name: basename(uri),331};332}333334export function toToolVariableEntry(entry: IToolData, range?: IOffsetRange): IChatRequestToolEntry {335return {336kind: 'tool',337id: entry.id,338icon: ThemeIcon.isThemeIcon(entry.icon) ? entry.icon : undefined,339name: entry.displayName,340value: undefined,341range342};343}344345export function toToolSetVariableEntry(entry: ToolSet, range?: IOffsetRange): IChatRequestToolSetEntry {346return {347kind: 'toolset',348id: entry.id,349icon: entry.icon,350name: entry.referenceName,351value: Array.from(entry.getTools()).map(t => toToolVariableEntry(t)),352range353};354}355356export class ChatRequestVariableSet {357private _ids = new Set<string>();358private _entries: IChatRequestVariableEntry[] = [];359360constructor(entries?: IChatRequestVariableEntry[]) {361if (entries) {362this.add(...entries);363}364}365366public add(...entry: IChatRequestVariableEntry[]): void {367for (const e of entry) {368if (!this._ids.has(e.id)) {369this._ids.add(e.id);370this._entries.push(e);371}372}373}374375public insertFirst(entry: IChatRequestVariableEntry): void {376if (!this._ids.has(entry.id)) {377this._ids.add(entry.id);378this._entries.unshift(entry);379}380}381382public remove(entry: IChatRequestVariableEntry): void {383this._ids.delete(entry.id);384this._entries = this._entries.filter(e => e.id !== entry.id);385}386387public has(entry: IChatRequestVariableEntry): boolean {388return this._ids.has(entry.id);389}390391public asArray(): IChatRequestVariableEntry[] {392return this._entries.slice(0); // return a copy393}394395public get length(): number {396return this._entries.length;397}398}399400401402