Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/inline/inlineChatFix3Prompt.tsx
13404 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
import * as l10n from '@vscode/l10n';
6
import { PromptElement, PromptReference, PromptSizing, SystemMessage, TextChunk, UserMessage } from '@vscode/prompt-tsx';
7
import type { CancellationToken, ChatResponseStream, ChatVulnerability, MarkdownString } from 'vscode';
8
import { IResponsePart } from '../../../../platform/chat/common/chatMLFetcher';
9
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
10
import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
11
import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';
12
import { ILanguageDiagnosticsService } from '../../../../platform/languages/common/languageDiagnosticsService';
13
import { KnownSources } from '../../../../platform/languageServer/common/languageContextService';
14
import { ILogService } from '../../../../platform/log/common/logService';
15
import { IParserService } from '../../../../platform/parser/node/parserService';
16
import { IPromptPathRepresentationService } from '../../../../platform/prompts/common/promptPathRepresentationService';
17
import { isNotebookCellOrNotebookChatInput } from '../../../../util/common/notebooks';
18
import { illegalArgument } from '../../../../util/vs/base/common/errors';
19
import { isEqual } from '../../../../util/vs/base/common/resources';
20
import { URI } from '../../../../util/vs/base/common/uri';
21
import { StringEdit } from '../../../../util/vs/editor/common/core/edits/stringEdit';
22
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
23
import { Range, TextEdit, Uri } from '../../../../vscodeTypes';
24
import { CodeBlockInfo, CodeBlockProcessor, isCodeBlockWithResource } from '../../../codeBlocks/node/codeBlockProcessor';
25
import { findDiagnosticForSelectionAndPrompt } from '../../../context/node/resolvers/fixSelection';
26
import { InlineFixProps } from '../../../context/node/resolvers/inlineFixIntentInvocation';
27
import { getStructure } from '../../../context/node/resolvers/selectionContextHelpers';
28
import { OutcomeAnnotation, OutcomeAnnotationLabel } from '../../../inlineChat/node/promptCraftingTypes';
29
import { IResponseProcessorContext, ReplyInterpreter, ReplyInterpreterMetaData } from '../../../prompt/node/intents';
30
import { CompositeElement } from '../base/common';
31
import { InstructionMessage } from '../base/instructionMessage';
32
import { LegacySafetyRules } from '../base/safetyRules';
33
import { Tag } from '../base/tag';
34
import { ICodeMapperService, IMapCodeRequest, IMapCodeResult } from '../codeMapper/codeMapperService';
35
import { getCustomMarker, getPatchEditReplyProcessor, PatchEditExamplePatch, PatchEditInputCodeBlock, PatchEditInputCodeBlockProps, PatchEditReplyProcessor, PatchEditRules } from '../codeMapper/patchEditGeneration';
36
import { ChatToolReferences, renderChatVariables, UserQuery } from '../panel/chatVariables';
37
import { CodeBlockFormattingRules } from '../panel/codeBlockFormattingRules';
38
import { HistoryWithInstructions } from '../panel/conversationHistory';
39
import { CustomInstructions } from '../panel/customInstructions';
40
import { CodeBlock } from '../panel/safeElements';
41
import { Diagnostics } from './diagnosticsContext';
42
import { InlineChatWorkspaceSearch } from './inlineChatWorkspaceSearch';
43
import { LanguageServerContextPrompt } from './languageServerContextPrompt';
44
import { ProjectedDocument } from './summarizedDocument/summarizeDocument';
45
import { summarizeDocumentSync } from './summarizedDocument/summarizeDocumentHelpers';
46
47
export class InlineFix3Prompt extends PromptElement<InlineFixProps> {
48
49
constructor(props: InlineFixProps,
50
@IIgnoreService private readonly ignoreService: IIgnoreService,
51
@IFileSystemService private readonly fileSystemService: IFileSystemService,
52
@IParserService private readonly parserService: IParserService,
53
@ILanguageDiagnosticsService private readonly languageDiagnosticsService: ILanguageDiagnosticsService,
54
@IConfigurationService private readonly configurationService: IConfigurationService,
55
@IInstantiationService private readonly instantiationService: IInstantiationService,
56
) {
57
super(props);
58
}
59
60
async render(state: void, sizing: PromptSizing) {
61
const { document, wholeRange, fileIndentInfo, selection, language } = this.props.documentContext;
62
const isIgnored = await this.ignoreService.isCopilotIgnored(document.uri);
63
if (isIgnored) {
64
return <ignoredFiles value={[document.uri]} />;
65
}
66
if (isNotebookCellOrNotebookChatInput(document.uri)) {
67
throw illegalArgument('InlineFix3PlusPrompt should not be used with a notebook!');
68
}
69
70
const inputDocCharLimit = (sizing.endpoint.modelMaxPromptTokens / 3) * 4; // consume one 3rd of the model window, estimating roughly 4 chars per token;
71
let projectedDocument: ProjectedDocument;
72
let isSummarized = false;
73
if (document.getText().length > inputDocCharLimit) {
74
// only compute the summarized document if needed
75
const structure = await getStructure(this.parserService, document, fileIndentInfo);
76
projectedDocument = summarizeDocumentSync(inputDocCharLimit, document, wholeRange, structure, { tryPreserveTypeChecking: true });
77
isSummarized = true;
78
} else {
79
projectedDocument = new ProjectedDocument(document.getText(), StringEdit.empty, document.languageId);
80
}
81
82
const { query, history, chatVariables, } = this.props.promptContext;
83
const { useWorkspaceChunksFromDiagnostics, useWorkspaceChunksFromSelection } = this.props.features;
84
85
const adjustedSelection = projectedDocument.projectRange(selection);
86
const selectedLinesContent = document.getText(new Range(selection.start.line, 0, selection.end.line + 1, 0)).trimEnd();
87
88
const diagnostics = findDiagnosticForSelectionAndPrompt(this.languageDiagnosticsService, document.uri, selection, query);
89
90
const enableCodeMapper = this.configurationService.getConfig(ConfigKey.TeamInternal.InlineChatUseCodeMapper);
91
92
const replyInterpreter = enableCodeMapper ?
93
this.instantiationService.createInstance(CodeMapperFixReplyInterpreter, document.uri) :
94
this.instantiationService.createInstance(PatchEditFixReplyInterpreter, projectedDocument, document.uri, adjustedSelection);
95
96
const GenerationRulesAndExample = enableCodeMapper ? CodeMapperRulesAndExample : PatchEditFixRulesAndExample;
97
const InputCodeBlock = enableCodeMapper ? CodeMapperInputCodeBlock : PatchEditInputCodeBlock;
98
99
const renderedChatVariables = await renderChatVariables(chatVariables, this.fileSystemService);
100
101
return (
102
<>
103
<references value={[new PromptReference(document.uri)]} />
104
<meta value={new ReplyInterpreterMetaData(replyInterpreter)} />
105
<SystemMessage priority={1000}>
106
You are an AI programming assistant.<br />
107
When asked for your name, you must respond with "GitHub Copilot".<br />
108
The user has a {language.languageId} file opened in a code editor.<br />
109
The user expects you to propose a fix for one or more problems in that file.<br />
110
<LegacySafetyRules />
111
</SystemMessage>
112
<HistoryWithInstructions inline={true} historyPriority={700} passPriority history={history}>
113
<InstructionMessage priority={1000}>
114
For the response always follow these instructions:<br />
115
Describe in a single sentence how you would solve the problem. After that sentence, add an empty line. Then provide code changes or a terminal command to run.<br />
116
<GenerationRulesAndExample />
117
</InstructionMessage>
118
</HistoryWithInstructions>
119
120
<UserMessage priority={700}>
121
<CustomInstructions /*priority={700}*/ languageId={language.languageId} chatVariables={chatVariables} />
122
<LanguageServerContextPrompt priority={700} document={document} position={selection.start} requestId={this.props.promptContext.requestId} source={KnownSources.fix} />
123
<CompositeElement priority={750} >{...renderedChatVariables}</CompositeElement>
124
<CompositeElement priority={600} >
125
{
126
projectedDocument.text.length > 0 ?
127
<>
128
I have the following code open in the editor, starting from line 1 to line {projectedDocument.lineCount}.<br />
129
</> :
130
<>
131
I am in an empty file:<br />
132
</>
133
}
134
<InputCodeBlock uri={document.uri} languageId={language.languageId} code={projectedDocument.text} shouldTrim={false} isSummarized={isSummarized} /><br />
135
</CompositeElement >
136
<CompositeElement /*priority={500}*/>
137
{
138
selection.isEmpty ?
139
<>
140
I have the selection at line {adjustedSelection.start.line + 1}, column {adjustedSelection.start.character + 1}<br />
141
</> :
142
<>
143
I have currently selected from line {adjustedSelection.start.line + 1}, column {adjustedSelection.start.character + 1} to line {adjustedSelection.end.line + 1} column {adjustedSelection.end.character + 1}.<br />
144
</>
145
}
146
</CompositeElement >
147
<CompositeElement /*priority={500}*/>
148
{
149
selectedLinesContent.length && !diagnostics.some(d => d.range.contains(selection)) &&
150
<>
151
The content of the lines at the selection is
152
<CodeBlock uri={document.uri} languageId={language.languageId} code={selectedLinesContent} shouldTrim={false} /><br />
153
</>
154
}
155
</CompositeElement >
156
<Diagnostics /*priority={500}*/ documentContext={this.props.documentContext} diagnostics={diagnostics} />
157
<InlineChatWorkspaceSearch /*priority={200}*/ diagnostics={diagnostics} documentContext={this.props.documentContext} useWorkspaceChunksFromDiagnostics={useWorkspaceChunksFromDiagnostics} useWorkspaceChunksFromSelection={useWorkspaceChunksFromSelection} />
158
<ChatToolReferences promptContext={this.props.promptContext} />
159
160
<Tag name='userPrompt'>
161
<TextChunk /*priority={700}*/>
162
Please find a fix for my code so that the result is without any errors.
163
</TextChunk>
164
<UserQuery chatVariables={chatVariables} query={query} /><br />
165
</Tag>
166
</UserMessage>
167
</>
168
);
169
}
170
}
171
172
const exampleUri = Uri.file('/someFolder/myFile.cs');
173
174
class PatchEditFixRulesAndExample extends PromptElement {
175
176
render() {
177
return (
178
<>
179
When proposing to fix the problem by running a terminal command, write `{getCustomMarker('TERMINAL')}` and provide a code block that starts with ```bash and contains the terminal script inside.<br />
180
<PatchEditRules />
181
<Tag name='example' priority={100}>
182
<Tag name='user'>
183
I have the following code open in the editor.<br />
184
<PatchEditInputCodeBlock
185
uri={exampleUri}
186
languageId='csharp'
187
code={['// This is my class', 'class C { }', '', 'new C().Field = 9;']}
188
/>
189
</Tag>
190
<Tag name='assistant'>
191
The problem is that the class 'C' does not have a field or property named 'Field'. To fix this, you need to add a 'Field' property to the 'C' class.<br />
192
<br />
193
<PatchEditExamplePatch
194
changes={
195
[
196
{
197
uri: exampleUri,
198
find: ['// This is my class', 'class C { }'],
199
replace: ['// This is my class', 'class C {', 'public int Field { get; set; }', '}']
200
},
201
{
202
uri: exampleUri,
203
find: ['new C().Field = 9;'],
204
replace: ['// set the field to 9', 'new C().Field = 9;']
205
}
206
]
207
}
208
/>
209
</Tag>
210
</Tag>
211
</>
212
);
213
}
214
}
215
216
export class PatchEditFixReplyInterpreter implements ReplyInterpreter {
217
private _lastText: string = '';
218
private readonly _replyProcessor: PatchEditReplyProcessor;
219
220
constructor(
221
private readonly projectedDocument: ProjectedDocument,
222
private readonly documentUri: URI,
223
private readonly adjustedSelection: Range,
224
@ILogService private readonly logService: ILogService,
225
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService
226
227
) {
228
this._replyProcessor = getPatchEditReplyProcessor(promptPathRepresentationService);
229
}
230
231
async processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, outputStream: ChatResponseStream, token: CancellationToken): Promise<void> {
232
let inFirstParagraph = true; // print only the frist paragraph
233
let charactersSent = 0;
234
let newText = '';
235
for await (const part of inputStream) {
236
if (token.isCancellationRequested) {
237
return;
238
}
239
newText += part.delta.text;
240
if (newText.length > this._lastText.length) {
241
this._lastText = newText; // the new complete text
242
if (inFirstParagraph) {
243
// test if the new text added made the first paragraph complete
244
const paragraph = this._replyProcessor.getFirstParagraph(newText);
245
if (paragraph.length > charactersSent) {
246
// still in the first paragraph
247
outputStream.markdown(paragraph.substring(charactersSent));
248
charactersSent = paragraph.length;
249
} else {
250
// the first paragraph is complete
251
inFirstParagraph = false;
252
outputStream.markdown('\n\n');
253
outputStream.progress(l10n.t('Generating edits...'));
254
}
255
}
256
}
257
}
258
if (this._lastText.length === 0) {
259
outputStream.warning(l10n.t('Copilot did not provide a response. Please try again.'));
260
return;
261
}
262
263
const res = this._replyProcessor.process(this._lastText, this.projectedDocument.text, this.documentUri, this.adjustedSelection.start.line);
264
if (res.otherSections.length) {
265
for (const section of res.otherSections) {
266
outputStream.markdown(section.content.join('\n\n'));
267
}
268
}
269
if (res.otherPatches.length) {
270
for (const patch of res.otherPatches) {
271
if (patch.replace.length) {
272
const uri = this.promptPathRepresentationService.resolveFilePath(patch.filePath, this.documentUri.scheme);
273
if (uri) {
274
outputStream.markdown(patch.replace[0]);
275
outputStream.codeblockUri(uri);
276
outputStream.markdown(patch.replace.slice(1).join('\n'));
277
} else {
278
outputStream.markdown(patch.replace.join('\n'));
279
}
280
}
281
}
282
}
283
let edits = res.edits;
284
if (edits.length) {
285
edits = this.projectedDocument.projectBackTextEdit(edits);
286
if (res.edits.length !== edits.length) {
287
res.annotations.push({ message: 'Some edits were not applied because they were out of bounds.', label: OutcomeAnnotationLabel.SUMMARIZE_CONFLICT, severity: 'error' });
288
} else {
289
const annot = this._validateTextEditProject(res.edits, edits, this.projectedDocument);
290
if (annot) {
291
res.annotations.push(annot);
292
}
293
}
294
}
295
context.addAnnotations(res.annotations);
296
if (edits.length) {
297
outputStream.textEdit(this.documentUri, edits);
298
} else if (!res.otherPatches.length && !res.otherSections.length) {
299
outputStream.warning(l10n.t('The edit generation was not successful. Please try again.'));
300
}
301
if (res.annotations.length) {
302
this.logService.info(`[inline fix] Problems generating edits: ${res.annotations.map(a => `${a.message} [${a.label}]`).join(', ')}, invalid patches: ${res.invalidPatches.length}`);
303
}
304
}
305
306
private _validateTextEditProject(edits: TextEdit[], projectedBackEdits: TextEdit[], projectedDocument: ProjectedDocument): OutcomeAnnotation | undefined {
307
for (let i = 0; i < edits.length; i++) {
308
const projEditString = projectedDocument.positionOffsetTransformer.toOffsetRange(edits[i].range).substring(projectedDocument.text);
309
const origEditString = projectedDocument.originalPositionOffsetTransformer.toOffsetRange(projectedBackEdits[i].range).substring(projectedDocument.originalText);
310
if (projEditString !== origEditString) {
311
return { message: `Problem projecting edits: '${projEditString}' does not match '${origEditString}' (projectecBack)`, label: OutcomeAnnotationLabel.INVALID_PROJECTION, severity: 'error' };
312
}
313
}
314
return undefined;
315
}
316
}
317
318
class CodeMapperInputCodeBlock extends PromptElement<PatchEditInputCodeBlockProps> {
319
render() {
320
return (
321
<CodeBlock
322
uri={this.props.uri}
323
languageId={this.props.languageId}
324
code={Array.isArray(this.props.code) ? this.props.code.join('\n') : this.props.code}
325
shouldTrim={this.props.shouldTrim}
326
includeFilepath={true}
327
/>
328
);
329
}
330
}
331
332
class CodeMapperRulesAndExample extends PromptElement {
333
render() {
334
return (
335
<>
336
When proposing to fix the problem by running a terminal command, provide a code block that starts with ```bash and contains the terminal script inside.<br />
337
<CodeBlockFormattingRules />
338
<Tag name='example' priority={100}>
339
<Tag name='user'>
340
I have the following code open in the editor.<br />
341
<CodeMapperInputCodeBlock
342
uri={exampleUri}
343
languageId='csharp'
344
code={['// This is my class', 'class C { }', '', 'new C().Field = 9;'].join('\n')}
345
shouldTrim={false}
346
/>
347
</Tag>
348
<Tag name='assistant'>
349
The problem is that the class 'C' does not have a field or property named 'Field'. To fix this, you need to add a 'Field' property to the 'C' class.<br />
350
<br />
351
<CodeMapperInputCodeBlock
352
uri={exampleUri}
353
languageId='csharp'
354
code={['// This is my class', 'class C {', ' public int Field { get; set; }', '}', ''].join('\n')}
355
shouldTrim={false}
356
/>
357
</Tag>
358
</Tag>
359
</>
360
);
361
}
362
}
363
364
class CodeMapperFixReplyInterpreter implements ReplyInterpreter {
365
366
constructor(
367
private readonly documentUri: URI,
368
@IPromptPathRepresentationService private readonly promptPathRepresentationService: IPromptPathRepresentationService,
369
@ICodeMapperService private readonly codeMapperService: ICodeMapperService,
370
) {
371
}
372
373
async processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, outputStream: ChatResponseStream, token: CancellationToken): Promise<void> {
374
let currentCodeBlock: CodeBlockInfo | undefined = undefined;
375
let applyCodeBlock = false;
376
let inFirstSentence = true;
377
const codeMapperWork: Promise<IMapCodeResult | undefined>[] = [];
378
const codeblockProcessor = new CodeBlockProcessor(
379
path => {
380
return this.promptPathRepresentationService.resolveFilePath(path);
381
},
382
(markdown: MarkdownString, codeBlockInfo: CodeBlockInfo | undefined, vulnerabilities: ChatVulnerability[] | undefined) => {
383
if (codeBlockInfo) {
384
inFirstSentence = false;
385
if (codeBlockInfo !== currentCodeBlock) {
386
// first time we see this code block
387
currentCodeBlock = codeBlockInfo;
388
applyCodeBlock = isEqual(codeBlockInfo.resource, this.documentUri);
389
if (!applyCodeBlock && codeBlockInfo.resource) {
390
outputStream.codeblockUri(codeBlockInfo.resource);
391
}
392
}
393
if (applyCodeBlock) {
394
return;
395
}
396
} else {
397
if (!inFirstSentence) {
398
return;
399
}
400
}
401
if (vulnerabilities) {
402
outputStream.markdownWithVulnerabilities(markdown, vulnerabilities);
403
} else {
404
outputStream.markdown(markdown);
405
}
406
},
407
codeBlock => {
408
if (isCodeBlockWithResource(codeBlock) && isEqual(codeBlock.resource, this.documentUri)) {
409
const request: IMapCodeRequest = { codeBlock };
410
outputStream.markdown('\n\n');
411
outputStream.progress(l10n.t('Generating edits...'));
412
const task = this.codeMapperService.mapCode(request, outputStream, { chatRequestId: context.turn.id, chatRequestSource: 'inline1Fix3', isAgent: false }, token).finally(() => {
413
if (!token.isCancellationRequested) {
414
// signal being done with this uri
415
outputStream.textEdit(codeBlock.resource, true);
416
}
417
});
418
codeMapperWork.push(task);
419
}
420
}
421
422
);
423
424
for await (const { delta } of inputStream) {
425
if (token.isCancellationRequested) {
426
return;
427
}
428
codeblockProcessor.processMarkdown(delta.text, delta.codeVulnAnnotations?.map(a => ({ title: a.details.type, description: a.details.description })));
429
}
430
codeblockProcessor.flush();
431
432
const results = await Promise.all(codeMapperWork);
433
for (const result of results) {
434
if (!result) {
435
context.addAnnotations([{ severity: 'error', label: 'cancelled', message: 'CodeMapper cancelled' }]);
436
} else if (result.annotations) {
437
context.addAnnotations(result.annotations);
438
}
439
}
440
for (const result of results) {
441
if (result && result.errorDetails) {
442
outputStream.warning(`CodeMapper error: ${result.errorDetails}`);
443
}
444
}
445
}
446
}
447
448