Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/node/devContainerConfigGenerator.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 type { CancellationToken } from 'vscode';
7
import { ChatFetchResponseType, ChatLocation } from '../../../platform/chat/common/commonTypes';
8
import { DevContainerConfigFeature, DevContainerConfigGeneratorResult, DevContainerConfigIndex, DevContainerConfigTemplate } from '../../../platform/devcontainer/common/devContainerConfigurationService';
9
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
10
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
11
import { escapeRegExpCharacters } from '../../../util/vs/base/common/strings';
12
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
13
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
14
import { DevContainerConfigPrompt } from '../../prompts/node/devcontainer/devContainerConfigPrompt';
15
16
const excludedTemplates = [
17
'alpine',
18
'debian',
19
'docker-existing-docker-compose',
20
'docker-existing-dockerfile',
21
'docker-in-docker',
22
'docker-outside-of-docker',
23
'docker-outside-of-docker-compose',
24
'ubuntu',
25
'universal',
26
].map(shortId => `ghcr.io/devcontainers/templates/${shortId}`);
27
28
const excludedFeatures = [
29
'common-utils',
30
'git',
31
].map(shortId => `ghcr.io/devcontainers/features/${shortId}`);
32
33
export class DevContainerConfigGenerator {
34
35
constructor(
36
@ITelemetryService private readonly telemetryService: ITelemetryService,
37
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
38
@IInstantiationService private readonly instantiationService: IInstantiationService,
39
) { }
40
41
async generate(index: DevContainerConfigIndex, filenames: string[], token: CancellationToken): Promise<DevContainerConfigGeneratorResult> {
42
if (!filenames.length) {
43
return {
44
type: 'success',
45
template: undefined,
46
features: [],
47
};
48
}
49
50
const startTime = Date.now();
51
52
const endpoint = await this.endpointProvider.getChatEndpoint('copilot-base');
53
const charLimit = Math.floor((endpoint.modelMaxPromptTokens * 4) / 3);
54
55
const processedFilenames = this.processFilenames(filenames, charLimit);
56
57
const templates = index.templates.filter(template => !excludedTemplates.includes(template.id));
58
const features = (index.features || []).filter(feature => !excludedFeatures.includes(feature.id));
59
60
const promptRenderer = PromptRenderer.create(this.instantiationService, endpoint, DevContainerConfigPrompt, {
61
filenames: processedFilenames,
62
templates,
63
features,
64
});
65
const prompt = await promptRenderer.render();
66
67
const requestStartTime = Date.now();
68
const fetchResult = await endpoint
69
.makeChatRequest(
70
'devContainerConfigGenerator',
71
prompt.messages,
72
undefined,
73
token,
74
ChatLocation.Other,
75
);
76
77
const suggestions = fetchResult.type === ChatFetchResponseType.Success ? this.processGeneratedConfig(fetchResult.value, templates, features) : undefined;
78
79
/* __GDPR__
80
"devcontainer.generateConfig" : {
81
"owner": "chrmarti",
82
"comment": "Metadata about the Dev Container Config generation",
83
"model": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The model that is used in the endpoint." },
84
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The id of the current request turn." },
85
"responseType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The result type of the response." },
86
"templateId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The chosen template id." },
87
"featureIds": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The chosen feature ids." },
88
"originalFilenameCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The number of filenames." },
89
"originalFilenameLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The length of the filenames." },
90
"processedFilenameCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The number of filenames after processing." },
91
"processedFilenameLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The length of the filenames after processing." },
92
"timeToRequest": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "How long it took to start the request." },
93
"timeToComplete": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "How long it took to complete the request." }
94
}
95
*/
96
this.telemetryService.sendMSFTTelemetryEvent('devcontainer.generateConfig', {
97
model: endpoint.model,
98
requestId: fetchResult.requestId,
99
responseType: fetchResult.type,
100
templateId: suggestions?.template,
101
featureIds: suggestions?.features.join(','),
102
}, {
103
originalFilenameCount: filenames.length,
104
originalFilenameLength: filenames.join('').length,
105
processedFilenameCount: processedFilenames.length,
106
processedFilenameLength: processedFilenames.join('').length,
107
timeToRequest: requestStartTime - startTime,
108
timeToComplete: Date.now() - startTime
109
});
110
111
return {
112
type: 'success',
113
template: suggestions?.template,
114
features: suggestions?.features || [],
115
};
116
}
117
118
private processFilenames(filenames: string[], charLimit: number): string[] {
119
const result: string[] = [...filenames];
120
121
// Reserve 10% of the character limit for the safety rules and instructions
122
const availableChars = Math.floor(charLimit * 0.9);
123
124
// Remove filenames if needed
125
let totalChars = result.join('\n').length;
126
if (totalChars > availableChars) {
127
// Remove filenames until we are under the character limit
128
while (totalChars > availableChars && result.length > 0) {
129
const lastDiff = result.pop()!;
130
totalChars -= lastDiff.length;
131
}
132
}
133
134
return result;
135
}
136
137
private processGeneratedConfig(message: string, availableTemplates: DevContainerConfigTemplate[], availableFeatures: DevContainerConfigFeature[]) {
138
let template = availableTemplates.find(t => new RegExp(`\\b${escapeRegExpCharacters(t.id)}\\b`).test(message))?.id;
139
if (template === 'ghcr.io/devcontainers/templates/javascript-node') {
140
template = 'ghcr.io/devcontainers/templates/typescript-node'; // Rarely suggested otherwise.
141
}
142
return {
143
template: template,
144
features: availableFeatures.filter(f => new RegExp(`\\b${escapeRegExpCharacters(f.id)}\\b`).test(message)).map(f => f.id),
145
};
146
}
147
}
148
149