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