Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/promptFileContext/vscode-node/promptFileContextService.ts
13399 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 * as vscode from 'vscode';
7
8
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
9
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
10
import { Copilot } from '../../../platform/inlineCompletions/common/api';
11
import { ILanguageContextProviderService, ProviderTarget } from '../../../platform/languageContextProvider/common/languageContextProviderService';
12
import { ILogService } from '../../../platform/log/common/logService';
13
import { PromptFileLangageId, PromptHeaderAttributes } from '../../../platform/promptFiles/common/promptsService';
14
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
15
import { Disposable, DisposableStore, IDisposable } from '../../../util/vs/base/common/lifecycle';
16
import { autorun, IObservable } from '../../../util/vs/base/common/observableInternal';
17
18
export const promptFileSelector = [PromptFileLangageId.prompt, PromptFileLangageId.instructions, PromptFileLangageId.agent];
19
20
export class PromptFileContextContribution extends Disposable {
21
22
private readonly _enableCompletionContext: IObservable<boolean>;
23
private registration: Promise<IDisposable> | undefined;
24
25
private models: string[] = ['GPT-4.1', 'GPT-4o'];
26
27
constructor(
28
@IConfigurationService configurationService: IConfigurationService,
29
@ILogService private readonly logService: ILogService,
30
@IExperimentationService experimentationService: IExperimentationService,
31
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
32
@ILanguageContextProviderService private readonly languageContextProviderService: ILanguageContextProviderService,
33
) {
34
super();
35
this._enableCompletionContext = configurationService.getExperimentBasedConfigObservable(ConfigKey.Advanced.PromptFileContext, experimentationService);
36
this._register(autorun(reader => {
37
if (this._enableCompletionContext.read(reader)) {
38
this.registration = this.register();
39
} else if (this.registration) {
40
this.registration.then(disposable => disposable.dispose());
41
this.registration = undefined;
42
}
43
}));
44
45
}
46
47
override dispose() {
48
super.dispose();
49
if (this.registration) {
50
this.registration.then(disposable => disposable.dispose());
51
this.registration = undefined;
52
}
53
}
54
55
private async register(): Promise<IDisposable> {
56
const disposables = new DisposableStore();
57
try {
58
const self = this;
59
const resolver: Copilot.ContextResolver<Copilot.SupportedContextItem> = {
60
async resolve(request: Copilot.ResolveRequest, token: vscode.CancellationToken): Promise<Copilot.SupportedContextItem[]> {
61
const [document, position] = self.getDocumentAndPosition(request, token);
62
if (document === undefined || position === undefined) {
63
return [];
64
}
65
const tokenBudget = self.getTokenBudget(document);
66
if (tokenBudget <= 0) {
67
return [];
68
}
69
return self.getContext(document.languageId);
70
}
71
};
72
73
this.endpointProvider.getAllChatEndpoints().then(endpoints => {
74
const modelNames = new Set<string>();
75
for (const endpoint of endpoints) {
76
if (endpoint.showInModelPicker) {
77
modelNames.add(endpoint.name);
78
}
79
}
80
this.models = [...modelNames.keys()];
81
});
82
83
const provider: Copilot.ContextProvider<Copilot.SupportedContextItem> = {
84
id: 'promptfile-ai-context-provider',
85
selector: promptFileSelector,
86
resolver: resolver
87
};
88
const copilotAPI = await this.getCopilotApi();
89
if (copilotAPI) {
90
disposables.add(copilotAPI.registerContextProvider(provider));
91
}
92
disposables.add(this.languageContextProviderService.registerContextProvider(provider, [ProviderTarget.NES, ProviderTarget.Completions]));
93
} catch (error) {
94
this.logService.error('Error regsistering prompt file context provider:', error);
95
}
96
return disposables;
97
}
98
99
private getContext(languageId: string): Copilot.SupportedContextItem[] {
100
101
102
switch (languageId) {
103
case PromptFileLangageId.prompt: {
104
const toolNamesList = this.getToolNames().join(', ');
105
return [
106
{
107
name: 'This is a prompt file. It uses markdown with a YAML front matter header that only supports a limited set of attributes and values. Do not suggest any other attributes',
108
value: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.agent, PromptHeaderAttributes.model, PromptHeaderAttributes.tools].join(', '),
109
},
110
{
111
name: '`agent` is optional and must be one of the following values',
112
value: `ask, edit or agent`,
113
},
114
{
115
name: '`model` is optional and must be one of the following values',
116
value: this.models.join(', '),
117
},
118
{
119
name: '`tools` is optional and must be an array of one or more of the following values. Do not make up any other tool names.',
120
value: toolNamesList
121
},
122
{
123
name: 'Here is an example of a prompt file',
124
value: [
125
``,
126
'```md',
127
`---`,
128
`agent: agent`,
129
`description: This prompt is used to generate a new issue template for GitHub repositories.`,
130
`model: ${this.models[0] || 'GPT-4.1'}`,
131
`tools: [${toolNamesList}]`,
132
`---`,
133
`Generate a new issue template for a GitHub repository.`,
134
'```',
135
].join('\n'),
136
},
137
];
138
}
139
case PromptFileLangageId.instructions: {
140
return [
141
{
142
name: 'This is a instructions file. It uses markdown with a YAML front matter header that only supports a limited set of attributes and values. Do not suggest any other properties',
143
value: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.applyTo].join(', ')
144
},
145
{
146
name: '`applyTo` is one or more glob patterns that specify which files the instructions apply to',
147
value: `**`,
148
},
149
{
150
name: 'Here is an example of an instruction file',
151
value: [
152
``,
153
'```md',
154
`---`,
155
`description: This file describes the TypeScript code style for the project.`,
156
`applyTo: **/*.ts, **/*.js`,
157
`---`,
158
`For private fields, start the field name with an underscore (_).`,
159
'```',
160
].join('\n'),
161
},
162
];
163
}
164
case PromptFileLangageId.agent: {
165
const toolNamesList = this.getToolNames().join(', ');
166
return [
167
{
168
name: 'This is a custom agent file. It uses markdown with a YAML front matter header that only supports a limited set of attributes and values. Do not suggest any other attributes',
169
value: [PromptHeaderAttributes.name, PromptHeaderAttributes.description, PromptHeaderAttributes.argumentHint, PromptHeaderAttributes.target, PromptHeaderAttributes.model, PromptHeaderAttributes.tools, PromptHeaderAttributes.handOffs].join(', '),
170
},
171
{
172
name: '`model` is optional and must be one of the following values',
173
value: this.models.join(', '),
174
},
175
{
176
name: '`tools` is optional and must be an array of one or more of the following values. Do not make up any other tool names.',
177
value: `[${toolNamesList}]`,
178
},
179
{
180
name: '`target` is optional and must be one of the following values',
181
value: `vscode, github-copilot`,
182
},
183
{
184
name: '`handoffs` is optional and is a sequence of mappings with `label`, `agent`, `prompt`, `send`, and `model` properties. The `model` property uses the format `Model Name (vendor)` (e.g., `GPT-4.1 (copilot)`)',
185
value: [
186
`handoffs:`,
187
` - label: Start Implementation`,
188
` agent: agent`,
189
` prompt: Implement the plan`,
190
` send: true`,
191
` model: GPT-4.1 (copilot)`,
192
].join('\n'),
193
},
194
{
195
name: 'Here is an example of a custom agent file',
196
value: [
197
``,
198
'```md',
199
`---`,
200
`description: This custom agent researches and plans new features for VS Code extensions.`,
201
`model: GPT-4.1`,
202
`tools: [${toolNamesList}]`,
203
`handoffs:`,
204
` - label: Start Implementation`,
205
` agent: agent`,
206
` prompt: Implement the plan`,
207
` send: true`,
208
` model: GPT-4.1 (copilot)`,
209
`---`,
210
`First come up with a plan for the new feature. Write a todo list of tasks to complete the feature.`,
211
'```',
212
].join('\n'),
213
},
214
];
215
}
216
default:
217
return [];
218
}
219
}
220
221
private getToolNames(): string[] {
222
return ['execute', 'read', 'edit', 'search', 'web', 'agent', 'todo'];
223
}
224
225
226
private async getCopilotApi(): Promise<Copilot.ContextProviderApiV1 | undefined> {
227
const copilotExtension = vscode.extensions.getExtension('GitHub.copilot');
228
if (copilotExtension === undefined) {
229
return undefined;
230
}
231
this.logService.info('Copilot extension found');
232
try {
233
const api = await copilotExtension.activate();
234
return api.getContextProviderAPI('v1');
235
} catch (error) {
236
if (error instanceof Error) {
237
this.logService.error('Error activating Copilot extension:', error.message);
238
} else {
239
this.logService.error('Error activating Copilot extension: Unknown error.');
240
}
241
return undefined;
242
}
243
}
244
245
public getTokenBudget(document: vscode.TextDocument): number {
246
return Math.trunc((8 * 1024) - (document.getText().length / 4) - 256);
247
}
248
249
private getDocumentAndPosition(request: Copilot.ResolveRequest, token?: vscode.CancellationToken): [vscode.TextDocument | undefined, vscode.Position | undefined] {
250
let document: vscode.TextDocument | undefined;
251
if (vscode.window.activeTextEditor?.document.uri.toString() === request.documentContext.uri) {
252
document = vscode.window.activeTextEditor.document;
253
} else {
254
document = vscode.workspace.textDocuments.find((doc) => doc.uri.toString() === request.documentContext.uri);
255
}
256
if (document === undefined) {
257
return [undefined, undefined];
258
}
259
const requestPos = request.documentContext.position;
260
const position = requestPos !== undefined ? new vscode.Position(requestPos.line, requestPos.character) : document.positionAt(request.documentContext.offset);
261
if (document.version > request.documentContext.version) {
262
if (!token?.isCancellationRequested) {
263
}
264
return [undefined, undefined];
265
}
266
if (document.version < request.documentContext.version) {
267
return [undefined, undefined];
268
}
269
return [document, position];
270
}
271
272
273
274
}
275
276