Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/customInstructions.tsx
13405 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 { BasePromptElementProps, PromptElement, PromptReference, PromptSizing, TextChunk } from '@vscode/prompt-tsx';6import type { ChatLanguageModelToolReference } from 'vscode';7import { ConfigKey } from '../../../../platform/configuration/common/configurationService';8import { CustomInstructionsKind, ICustomInstructions, ICustomInstructionsService } from '../../../../platform/customInstructions/common/customInstructionsService';9import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';10import { ILogService } from '../../../../platform/log/common/logService';11import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';12import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';13import { ResourceSet } from '../../../../util/vs/base/common/map';14import { URI } from '../../../../util/vs/base/common/uri';15import { ChatVariablesCollection, isCustomizationsIndex, isInstructionFile } from '../../../prompt/common/chatVariablesCollection';16import { IPromptVariablesService } from '../../../prompt/node/promptVariablesService';17import { Tag } from '../base/tag';1819export interface CustomInstructionsProps extends BasePromptElementProps {20readonly chatVariables: ChatVariablesCollection | undefined;2122readonly languageId: string | undefined;23/**24* @default true25*/26readonly includeCodeGenerationInstructions?: boolean;27/**28* @default false29*/30readonly includeTestGenerationInstructions?: boolean;31/**32* @default false33*/34readonly includeCodeFeedbackInstructions?: boolean;35/**36* @default false37*/38readonly includeCommitMessageGenerationInstructions?: boolean;39/**40* @default false41*/42readonly includePullRequestDescriptionGenerationInstructions?: boolean;43readonly customIntroduction?: string;4445/**46* @default true47*/48readonly includeSystemMessageConflictWarning?: boolean;49}5051export class CustomInstructions extends PromptElement<CustomInstructionsProps> {52constructor(53props: CustomInstructionsProps,54@ICustomInstructionsService private readonly customInstructionsService: ICustomInstructionsService,55@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,56@IPromptVariablesService private readonly promptVariablesService: IPromptVariablesService,57@IFileSystemService private readonly fileSystemService: IFileSystemService,58@ILogService private readonly logService: ILogService,59@IWorkspaceService private readonly workspaceService: IWorkspaceService,60) {61super(props);62}63override async render(state: void, sizing: PromptSizing) {6465const { includeCodeGenerationInstructions, includeTestGenerationInstructions, includeCodeFeedbackInstructions, includeCommitMessageGenerationInstructions, includePullRequestDescriptionGenerationInstructions, customIntroduction } = this.props;66const includeSystemMessageConflictWarning = this.props.includeSystemMessageConflictWarning ?? true;6768const chunks = [];6970if (includeCodeGenerationInstructions !== false) {71const hasSeen = new ResourceSet();72const hasSeenContent = new Set();73if (this.props.chatVariables) {74for (const variable of this.props.chatVariables) {75if (isCustomizationsIndex(variable)) {76let value = variable.value;77if (variable.reference.toolReferences?.length) {78value = await this.promptVariablesService.resolveToolReferencesInPrompt(value, variable.reference.toolReferences);79}80chunks.push(<TextChunk>{value}</TextChunk>);81} else if (isInstructionFile(variable)) {82const value = variable.value;83if (!hasSeen.has(value)) {84hasSeen.add(value);85const element = await this.createElementFromURI(value, variable.reference.toolReferences);86if (element && !hasSeenContent.has(element.content)) {87hasSeenContent.add(element.content);88chunks.push(element.chuck);89}90}91}92}93}94const instructionFiles = await this.customInstructionsService.getAgentInstructions();95for (const instructionFile of instructionFiles) {96if (!hasSeen.has(instructionFile)) {97hasSeen.add(instructionFile);98const element = await this.createElementFromURI(instructionFile);99if (element && !hasSeenContent.has(element.content)) {100hasSeenContent.add(element.content);101chunks.push(element.chuck);102}103}104}105}106107const customInstructions: ICustomInstructions[] = [];108if (includeCodeGenerationInstructions !== false) {109customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.CodeGenerationInstructions));110}111if (includeTestGenerationInstructions) {112customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.TestGenerationInstructions));113}114if (includeCodeFeedbackInstructions) {115customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.CodeFeedbackInstructions));116}117if (includeCommitMessageGenerationInstructions) {118customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.CommitMessageGenerationInstructions));119}120if (includePullRequestDescriptionGenerationInstructions) {121customInstructions.push(...await this.customInstructionsService.fetchInstructionsFromSetting(ConfigKey.PullRequestDescriptionGenerationInstructions));122}123for (const instruction of customInstructions) {124const chunk = this.createInstructionElement(instruction);125if (chunk) {126chunks.push(chunk);127}128}129if (chunks.length === 0) {130return undefined;131}132const introduction = customIntroduction ?? 'When generating code, please follow these user provided coding instructions.';133const isMultiRoot = this.workspaceService.getWorkspaceFolders().length > 1;134const multiRootHint = isMultiRoot && ' This is a multi-root workspace. The instructions below may come from different workspace folders. Apply each set of instructions to the folder it belongs to.';135const systemMessageConflictWarning = includeSystemMessageConflictWarning && ' You can ignore an instruction if it contradicts a system message.';136137return (<>138{introduction}{multiRootHint}{systemMessageConflictWarning}<br />139<Tag name='instructions'>140{141...chunks142}143</Tag>144145</>);146}147148private async createElementFromURI(fileUri: URI, toolReferences?: readonly ChatLanguageModelToolReference[]): Promise<{ chuck: PromptElement; content: string } | undefined> {149try {150const fileContents = await this.fileSystemService.readFile(fileUri);151let content = new TextDecoder().decode(fileContents);152if (toolReferences && toolReferences.length > 0) {153content = await this.promptVariablesService.resolveToolReferencesInPrompt(content, toolReferences);154}155content = content.trim();156if (content.length === 0) {157return undefined;158}159const attrs: Record<string, string> = { filePath: this.promptPathRepresentationService.getFilePath(fileUri) };160const folders = this.workspaceService.getWorkspaceFolders();161if (folders.length > 1) {162const folder = this.workspaceService.getWorkspaceFolder(fileUri);163if (folder) {164attrs.workspaceFolder = this.workspaceService.getWorkspaceFolderName(folder);165}166}167return {168chuck: <Tag name='attachment' attrs={attrs}>169<references value={[new InstructionFileReference(fileUri, content)]} />170<TextChunk>{content}</TextChunk>171</Tag>,172content173};174} catch (e) {175this.logService.debug(`Instruction file not found: ${fileUri.toString()}`);176return undefined;177}178}179180private createInstructionElement(instructions: ICustomInstructions) {181const lines = [];182for (const entry of instructions.content) {183if (entry.languageId) {184if (entry.languageId === this.props.languageId) {185lines.push(`For ${entry.languageId} code: ${entry.instruction}`);186}187} else {188lines.push(entry.instruction);189}190}191if (lines.length === 0) {192return undefined;193}194195return (<>196<references value={[new CustomInstructionPromptReference(instructions, lines)]} />197<>198{199lines.map(line => <TextChunk>{line}</TextChunk>)200}201</>202</>);203}204205}206207export class CustomInstructionPromptReference extends PromptReference {208constructor(public readonly instructions: ICustomInstructions, public readonly usedInstructions: string[]) {209super(instructions.reference);210}211}212213export class InstructionFileReference extends PromptReference {214constructor(public readonly ref: URI, public readonly instruction: string) {215super(ref);216}217}218219export function getCustomInstructionTelemetry(references: readonly PromptReference[]): { codeGenInstructionsCount: number; codeGenInstructionsLength: number; codeGenInstructionsFilteredCount: number; codeGenInstructionFileCount: number; codeGenInstructionSettingsCount: number } {220let codeGenInstructionsCount = 0;221let codeGenInstructionsFilteredCount = 0;222let codeGenInstructionsLength = 0;223let codeGenInstructionFileCount = 0;224let codeGenInstructionSettingsCount = 0;225226for (const reference of references) {227if (reference instanceof CustomInstructionPromptReference) {228codeGenInstructionsCount += reference.usedInstructions.length;229codeGenInstructionsLength += reference.usedInstructions.reduce((acc, instruction) => acc + instruction.length, 0);230codeGenInstructionsFilteredCount += Math.max(reference.instructions.content.length - reference.usedInstructions.length, 0);231if (reference.instructions.kind === CustomInstructionsKind.File) {232codeGenInstructionFileCount++;233} else {234codeGenInstructionSettingsCount += reference.usedInstructions.length;235}236} else if (reference instanceof InstructionFileReference) {237codeGenInstructionsLength += reference.instruction.length;238codeGenInstructionsCount++;239codeGenInstructionFileCount++;240}241}242return { codeGenInstructionsCount, codeGenInstructionsLength, codeGenInstructionsFilteredCount, codeGenInstructionFileCount, codeGenInstructionSettingsCount };243244}245246247