Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/intents/node/testIntent/testFromTestInvocation.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 { PromptElement, PromptElementProps, PromptSizing, SystemMessage, UserMessage } from '@vscode/prompt-tsx';
7
import type * as vscode from 'vscode';
8
import { IResponsePart } from '../../../../platform/chat/common/chatMLFetcher';
9
import { ChatLocation } from '../../../../platform/chat/common/commonTypes';
10
import { IChatEndpoint } from '../../../../platform/networking/common/networking';
11
import { IParserService } from '../../../../platform/parser/node/parserService';
12
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
13
import { isNotebookCellOrNotebookChatInput } from '../../../../util/common/notebooks';
14
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
15
import { illegalArgument } from '../../../../util/vs/base/common/errors';
16
import { assertType } from '../../../../util/vs/base/common/types';
17
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
18
import { IBuildPromptContext } from '../../../prompt/common/intents';
19
import { IDocumentContext } from '../../../prompt/node/documentContext';
20
import { IIntentInvocation, IResponseProcessorContext, ReplyInterpreter, ReplyInterpreterMetaData } from '../../../prompt/node/intents';
21
import { Test2Impl } from '../../../prompt/node/test2Impl';
22
import { CopilotIdentityRules } from '../../../prompts/node/base/copilotIdentity';
23
import { InstructionMessage } from '../../../prompts/node/base/instructionMessage';
24
import { PromptRenderer } from '../../../prompts/node/base/promptRenderer';
25
import { SafetyRules } from '../../../prompts/node/base/safetyRules';
26
import { Tag } from '../../../prompts/node/base/tag';
27
import { ChatToolReferences, ChatVariables, UserQuery } from '../../../prompts/node/panel/chatVariables';
28
import { HistoryWithInstructions } from '../../../prompts/node/panel/conversationHistory';
29
import { CustomInstructions } from '../../../prompts/node/panel/customInstructions';
30
import { CodeBlock } from '../../../prompts/node/panel/safeElements';
31
import { SelectionSplitKind, SummarizedDocumentData, SummarizedDocumentWithSelection } from './summarizedDocumentWithSelection';
32
import { TestDeps } from './testDeps';
33
import { ITestGenInfo, ITestGenInfoStorage } from './testInfoStorage';
34
import { TestsIntent } from './testIntent';
35
import { formatRequestAndUserQuery } from './testPromptUtil';
36
import { PseudoStopStartResponseProcessor } from '../../../prompt/node/pseudoStartStopConversationCallback';
37
38
39
/**
40
* Invoke from within a test file
41
*/
42
export class TestFromTestInvocation implements IIntentInvocation {
43
44
private replyInterpreter: ReplyInterpreter | null = null;
45
46
constructor(
47
readonly intent: TestsIntent,
48
readonly endpoint: IChatEndpoint,
49
readonly location: ChatLocation,
50
private readonly context: IDocumentContext,
51
private readonly alreadyConsumedChatVariable: vscode.ChatPromptReference | undefined,
52
@IInstantiationService private readonly instantiationService: IInstantiationService,
53
@ITestGenInfoStorage private readonly testGenInfoStorage: ITestGenInfoStorage,
54
) {
55
}
56
57
async buildPrompt(
58
promptContext: IBuildPromptContext,
59
progress: vscode.Progress<
60
vscode.ChatResponseProgressPart | vscode.ChatResponseReferencePart
61
>,
62
token: vscode.CancellationToken
63
) {
64
const testGenInfo = this.testGenInfoStorage.sourceFileToTest;
65
66
if (testGenInfo !== undefined) {
67
this.testGenInfoStorage.sourceFileToTest = undefined;
68
}
69
70
const renderer = PromptRenderer.create(
71
this.instantiationService,
72
this.endpoint,
73
TestFromTestPrompt,
74
{
75
context: this.context,
76
promptContext,
77
alreadyConsumedChatVariable: this.alreadyConsumedChatVariable,
78
testGenInfo,
79
}
80
);
81
82
const result = await renderer.render(progress, token);
83
84
this.replyInterpreter = result.metadata.get(ReplyInterpreterMetaData)?.replyInterpreter ?? null;
85
86
return result;
87
}
88
89
async processResponse(
90
context: IResponseProcessorContext,
91
inputStream: AsyncIterable<IResponsePart>,
92
outputStream: vscode.ChatResponseStream,
93
token: CancellationToken
94
): Promise<vscode.ChatResult | void> {
95
96
if (this.location === ChatLocation.Panel) {
97
const responseProcessor = this.instantiationService.createInstance(PseudoStopStartResponseProcessor, [], undefined);
98
await responseProcessor.processResponse(context, inputStream, outputStream, token);
99
return;
100
}
101
102
assertType(this.replyInterpreter !== null, 'TestFromTestInvocation should have received replyInterpreter from its prompt element');
103
104
return this.replyInterpreter.processResponse(
105
context,
106
inputStream,
107
outputStream,
108
token
109
);
110
}
111
}
112
113
type Props = PromptElementProps<{
114
context: IDocumentContext;
115
promptContext: IBuildPromptContext;
116
alreadyConsumedChatVariable: vscode.ChatPromptReference | undefined;
117
testGenInfo: ITestGenInfo | undefined;
118
}>;
119
120
class TestFromTestPrompt extends PromptElement<Props> {
121
122
constructor(
123
props: Props,
124
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
125
@IParserService private readonly parserService: IParserService
126
) {
127
super(props);
128
}
129
130
override async render(_state: void, sizing: PromptSizing) {
131
132
const { history, query, chatVariables, } = this.props.promptContext;
133
const { context, testGenInfo, alreadyConsumedChatVariable, } = this.props;
134
135
if (isNotebookCellOrNotebookChatInput(context.document.uri)) {
136
throw illegalArgument('TestFromTestPrompt should not be used for notebooks');
137
}
138
139
const testedSymbolIdentifier = testGenInfo?.identifier;
140
141
const requestAndUserQuery = testGenInfo === undefined
142
? `Please, generate more tests, taking into account existing tests. ${query}`.trim()
143
: formatRequestAndUserQuery({
144
workspaceService: this.workspaceService,
145
chatVariables,
146
userQuery: query,
147
testFileToWriteTo: context.document.uri,
148
testedSymbolIdentifier,
149
context,
150
});
151
152
let testedDeclarationExcerpt = undefined;
153
if (testGenInfo !== undefined) {
154
const srcFileDoc = await this.workspaceService.openTextDocument(testGenInfo.uri);
155
const declStart = testGenInfo.target.start;
156
const expandedRange = testGenInfo.target.with(declStart.with(declStart.line, 0));
157
testedDeclarationExcerpt = srcFileDoc.getText(expandedRange);
158
}
159
160
const data = await SummarizedDocumentData.create(
161
this.parserService,
162
context.document,
163
context.fileIndentInfo,
164
context.wholeRange,
165
SelectionSplitKind.Adjusted,
166
);
167
168
const filteredChatVariables = alreadyConsumedChatVariable === undefined ? chatVariables : chatVariables.filter(v => v.reference !== alreadyConsumedChatVariable);
169
170
return (
171
<>
172
<SystemMessage priority={1000}>
173
You are an AI programming assistant.<br />
174
<CopilotIdentityRules /><br />
175
<SafetyRules />
176
</SystemMessage>
177
<HistoryWithInstructions passPriority history={history} historyPriority={700}>
178
<InstructionMessage priority={1000}>
179
The user has a {context.language.languageId} file opened in a code editor.<br />
180
The user includes some code snippets from the file.<br />
181
Answer with a single {context.language.languageId} code block.<br />
182
Your expertise is strictly limited to software development topics.<br />
183
</InstructionMessage>
184
</HistoryWithInstructions>
185
<UserMessage>
186
<TestDeps priority={750} languageId={context.language.languageId} />
187
<CustomInstructions chatVariables={filteredChatVariables} priority={725} languageId={context.language.languageId} includeTestGenerationInstructions={true} />
188
189
<ChatToolReferences priority={750} promptContext={this.props.promptContext} flexGrow={1} />
190
<ChatVariables priority={750} chatVariables={filteredChatVariables} />
191
192
{/* include summarized source file: */}
193
<Test2Impl priority={800} documentContext={context} srcFile={testGenInfo} />
194
{/* include summarized test file: */}
195
<Tag name='testsFile' priority={900}>
196
<SummarizedDocumentWithSelection
197
documentData={data}
198
tokenBudget={sizing.tokenBudget / 3}
199
_allowEmptySelection={true}
200
/>{ /* FIXME@ulugbekna: rework summarization to be more intelligent */}
201
{/* repeat tested declaration -- otherwise, model seems to forget it: */}
202
</Tag>
203
{testGenInfo !== undefined && testedDeclarationExcerpt !== undefined && /* FIXME@ulugbekna: include class around */
204
<Tag name='codeToTest' priority={900}>
205
{`Repeating excerpt from \`${testGenInfo?.uri.path}\` here that needs to be tested:`}{/* FIXME@ulugbekna */}<br />
206
<CodeBlock uri={testGenInfo.uri} languageId={context.language.languageId} code={testedDeclarationExcerpt} />
207
</Tag>}
208
<Tag name='userPrompt' priority={900}>
209
<UserQuery chatVariables={filteredChatVariables} query={requestAndUserQuery} />
210
</Tag>
211
</UserMessage>
212
</>
213
);
214
}
215
}
216
217