Skip to content

Commit f6ba5f4

Browse files
committed
codesnippets v2 changes
1 parent 799d09f commit f6ba5f4

File tree

3 files changed

+88
-40
lines changed

3 files changed

+88
-40
lines changed

Extension/src/LanguageServer/client.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ export interface CopilotCompletionContextParams {
585585
uri: string;
586586
caretOffset: number;
587587
featureFlag: CopilotCompletionContextFeatures;
588+
maxSnippetCount: number;
589+
maxSnippetLength: number;
588590
}
589591

590592
// Requests
@@ -843,7 +845,7 @@ export interface Client {
843845
getIncludes(uri: vscode.Uri, maxDepth: number): Promise<GetIncludesResult>;
844846
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult>;
845847
filesEncodingChanged(filesEncodingChanged: FilesEncodingChanged): void;
846-
getCompletionContext(fileName: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures, token: vscode.CancellationToken): Promise<CopilotCompletionContextResult>;
848+
getCompletionContext(fileName: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures, maxSnippetCount: number, maxSnippetLength: number, token: vscode.CancellationToken): Promise<CopilotCompletionContextResult>;
847849
}
848850

849851
export function createClient(workspaceFolder?: vscode.WorkspaceFolder): Client {
@@ -2352,11 +2354,12 @@ export class DefaultClient implements Client {
23522354
}
23532355

23542356
public async getCompletionContext(file: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures,
2357+
maxSnippetCount: number, maxSnippetLength: number,
23552358
token: vscode.CancellationToken): Promise<CopilotCompletionContextResult> {
23562359
await withCancellation(this.ready, token);
23572360
return DefaultClient.withLspCancellationHandling(
23582361
() => this.languageClient.sendRequest(CopilotCompletionContextRequest,
2359-
{ uri: file.toString(), caretOffset, featureFlag }, token), token);
2362+
{ uri: file.toString(), caretOffset, featureFlag, maxSnippetCount, maxSnippetLength }, token), token);
23602363
}
23612364

23622365
/**
@@ -4277,5 +4280,5 @@ class NullClient implements Client {
42774280
getIncludes(uri: vscode.Uri, maxDepth: number): Promise<GetIncludesResult> { return Promise.resolve({} as GetIncludesResult); }
42784281
getChatContext(uri: vscode.Uri, token: vscode.CancellationToken): Promise<ChatContextResult> { return Promise.resolve({} as ChatContextResult); }
42794282
filesEncodingChanged(filesEncodingChanged: FilesEncodingChanged): void { }
4280-
getCompletionContext(file: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures, token: vscode.CancellationToken): Promise<CopilotCompletionContextResult> { return Promise.resolve({} as CopilotCompletionContextResult); }
4283+
getCompletionContext(file: vscode.Uri, caretOffset: number, featureFlag: CopilotCompletionContextFeatures, maxSnippetCount: number, maxSnippetLength: number, token: vscode.CancellationToken): Promise<CopilotCompletionContextResult> { return Promise.resolve({} as CopilotCompletionContextResult); }
42814284
}

Extension/src/LanguageServer/copilotCompletionContextProvider.ts

+76-33
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,19 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
7575
private static readonly providerId = 'ms-vscode.cpptools';
7676
private readonly completionContextCache: Map<string, CacheEntry> = new Map();
7777
private static readonly defaultCppDocumentSelector: DocumentSelector = [{ language: 'cpp' }, { language: 'c' }, { language: 'cuda-cpp' }];
78-
// A percentage expressed as an integer number, i.e. 50 means 50%.
79-
private static readonly defaultTimeBudgetFactor: number = 50;
80-
private static readonly defaultMaxCaretDistance = 4096;
78+
// The default time budget for providing a value from resolve().
79+
private static readonly defaultTimeBudgetMs: number = 7;
80+
// Assume the cache is stale when the distance to the current caret is greater than this value.
81+
private static readonly defaultMaxCaretDistance = 2048;
82+
private static readonly defaultMaxSnippetCount = 50 * 1024; // 50KB
83+
private static readonly defaultMaxSnippetLength = 10 * 1024; // 10KB
8184
private completionContextCancellation = new vscode.CancellationTokenSource();
8285
private contextProviderDisposable: vscode.Disposable | undefined;
86+
static readonly CppContextProviderEnabledFeatures = 'enabledFeatures';
87+
static readonly CppContextProviderTimeBudgetMs = 'timeBudgetMs';
88+
static readonly CppContextProviderMaxSnippetCount = 'maxSnippetCount';
89+
static readonly CppContextProviderMaxSnippetLength = 'maxSnippetLength';
90+
static readonly CppContextProviderMaxDistanceToCaret = 'maxDistanceToCaret';
8391

8492
constructor(private readonly logger: Logger) {
8593
}
@@ -125,7 +133,8 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
125133
// Get the completion context with a timeout and a cancellation token.
126134
// The cancellationToken indicates that the value should not be returned nor cached.
127135
private async getCompletionContextWithCancellation(context: ResolveRequest, featureFlag: CopilotCompletionContextFeatures,
128-
startTime: number, telemetry: CopilotCompletionContextTelemetry, internalToken: vscode.CancellationToken):
136+
maxSnippetCount: number, maxSnippetLength: number, startTime: number, telemetry: CopilotCompletionContextTelemetry,
137+
internalToken: vscode.CancellationToken):
129138
Promise<CopilotCompletionContextResult | undefined> {
130139
const documentUri = context.documentContext.uri;
131140
const caretOffset = context.documentContext.offset;
@@ -143,7 +152,7 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
143152
const getCompletionContextStartTime = performance.now();
144153

145154
const copilotCompletionContext: CopilotCompletionContextResult =
146-
await client.getCompletionContext(docUri, caretOffset, snippetsFeatureFlag, internalToken);
155+
await client.getCompletionContext(docUri, caretOffset, snippetsFeatureFlag, maxSnippetCount, maxSnippetLength, internalToken);
147156
telemetry.addRequestId(copilotCompletionContext.requestId);
148157
logMessage += `(id:${copilotCompletionContext.requestId}) (getClientFor elapsed:${getClientForDuration}ms)`;
149158
if (!copilotCompletionContext.areSnippetsMissing) {
@@ -187,38 +196,56 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
187196
telemetry.send("cache");
188197
}
189198
}
190-
static readonly CppCodeSnippetsEnabledFeatures = 'CppCodeSnippetsEnabledFeatures';
191-
static readonly CppCodeSnippetsTimeBudgetFactor = 'CppCodeSnippetsTimeBudgetFactor';
192-
static readonly CppCodeSnippetsMaxDistanceToCaret = 'CppCodeSnippetsMaxDistanceToCaret';
193199

194-
private async fetchTimeBudgetFactor(context: ResolveRequest): Promise<number> {
200+
private async fetchTimeBudgetMs(context: ResolveRequest): Promise<number> {
195201
try {
196-
const budgetFactor = context.activeExperiments.get(CopilotCompletionContextProvider.CppCodeSnippetsTimeBudgetFactor);
197-
return (isNumber(budgetFactor) ? budgetFactor : CopilotCompletionContextProvider.defaultTimeBudgetFactor) / 100.0;
202+
const timeBudgetMs = context.activeExperiments.get(CopilotCompletionContextProvider.CppContextProviderTimeBudgetMs);
203+
return isNumber(timeBudgetMs) ? timeBudgetMs : CopilotCompletionContextProvider.defaultTimeBudgetMs;
198204
} catch (e) {
199-
console.warn(`fetchTimeBudgetFactor(): error fetching ${CopilotCompletionContextProvider.CppCodeSnippetsTimeBudgetFactor}, using default: `, e);
200-
return CopilotCompletionContextProvider.defaultTimeBudgetFactor;
205+
console.warn(`fetchTimeBudgetMs(): error fetching ${CopilotCompletionContextProvider.CppContextProviderTimeBudgetMs}, using default: `, e);
206+
return CopilotCompletionContextProvider.defaultTimeBudgetMs;
201207
}
202208
}
203209

204210
private async fetchMaxDistanceToCaret(context: ResolveRequest): Promise<number> {
205211
try {
206-
const maxDistance = context.activeExperiments.get(CopilotCompletionContextProvider.CppCodeSnippetsMaxDistanceToCaret);
212+
const maxDistance = context.activeExperiments.get(CopilotCompletionContextProvider.CppContextProviderMaxDistanceToCaret);
207213
return isNumber(maxDistance) ? maxDistance : CopilotCompletionContextProvider.defaultMaxCaretDistance;
208214
} catch (e) {
209-
console.warn(`fetchMaxDistanceToCaret(): error fetching ${CopilotCompletionContextProvider.CppCodeSnippetsMaxDistanceToCaret}, using default: `, e);
215+
console.warn(`fetchMaxDistanceToCaret(): error fetching ${CopilotCompletionContextProvider.CppContextProviderMaxDistanceToCaret}, using default: `, e);
210216
return CopilotCompletionContextProvider.defaultMaxCaretDistance;
211217
}
212218
}
213219

220+
private async fetchMaxSnippetCount(context: ResolveRequest): Promise<number> {
221+
try {
222+
const maxSnippetCount = context.activeExperiments.get(CopilotCompletionContextProvider.CppContextProviderMaxSnippetCount);
223+
return isNumber(maxSnippetCount) ? maxSnippetCount : CopilotCompletionContextProvider.defaultMaxSnippetCount;
224+
} catch (e) {
225+
console.warn(`fetchMaxSnippetCount(): error fetching ${CopilotCompletionContextProvider.defaultMaxSnippetCount}, using default: `, e);
226+
return CopilotCompletionContextProvider.defaultMaxSnippetCount;
227+
}
228+
}
229+
230+
private async fetchMaxSnippetLength(context: ResolveRequest): Promise<number> {
231+
try {
232+
const maxSnippetLength = context.activeExperiments.get(CopilotCompletionContextProvider.CppContextProviderMaxSnippetLength);
233+
return isNumber(maxSnippetLength) ? maxSnippetLength : CopilotCompletionContextProvider.defaultMaxSnippetLength;
234+
} catch (e) {
235+
console.warn(`fetchMaxSnippetLength(): error fetching ${CopilotCompletionContextProvider.defaultMaxSnippetLength}, using default: `, e);
236+
return CopilotCompletionContextProvider.defaultMaxSnippetLength;
237+
}
238+
}
239+
214240
private async getEnabledFeatureNames(context: ResolveRequest): Promise<string[] | undefined> {
215241
try {
216-
const enabledFeatureNames = new CppSettings().cppCodeSnippetsFeatureNames ?? context.activeExperiments.get(CopilotCompletionContextProvider.CppCodeSnippetsEnabledFeatures);
242+
const enabledFeatureNames = new CppSettings().cppCodeSnippetsFeatureNames ??
243+
context.activeExperiments.get(CopilotCompletionContextProvider.CppContextProviderEnabledFeatures);
217244
if (isString(enabledFeatureNames)) {
218245
return enabledFeatureNames.split(',').map(s => s.trim());
219246
}
220247
} catch (e) {
221-
console.warn(`getEnabledFeatures(): error fetching ${CopilotCompletionContextProvider.CppCodeSnippetsEnabledFeatures}: `, e);
248+
console.warn(`getEnabledFeatureNames(): error fetching ${CopilotCompletionContextProvider.CppContextProviderEnabledFeatures}: `, e);
222249
}
223250
return undefined;
224251
}
@@ -251,11 +278,31 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
251278
this.completionContextCache.delete(fileUri);
252279
}
253280

281+
private computeSnippetsResolved: boolean = true;
282+
283+
private async resolveResultAndKind(context: ResolveRequest, featureFlag: CopilotCompletionContextFeatures,
284+
telemetry: CopilotCompletionContextTelemetry, defaultValue: CopilotCompletionContextResult | undefined,
285+
resolveStartTime: number, timeBudgetMs: number, maxSnippetCount: number, maxSnippetLength: number,
286+
copilotCancel: vscode.CancellationToken): Promise<[CopilotCompletionContextResult | undefined, CopilotCompletionKind]> {
287+
if (this.computeSnippetsResolved) {
288+
this.computeSnippetsResolved = false;
289+
const computeSnippetsPromise = this.getCompletionContextWithCancellation(context, featureFlag,
290+
maxSnippetCount, maxSnippetLength, resolveStartTime, telemetry.fork(), this.completionContextCancellation.token).finally(
291+
() => this.computeSnippetsResolved = true
292+
);
293+
const res = await this.waitForCompletionWithTimeoutAndCancellation(
294+
computeSnippetsPromise, defaultValue, timeBudgetMs, copilotCancel);
295+
return res;
296+
} else { return [defaultValue, defaultValue ? CopilotCompletionKind.GotFromCache : CopilotCompletionKind.MissingCacheMiss]; }
297+
}
298+
254299
public async resolve(context: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise<SupportedContextItem[]> {
255300
const resolveStartTime = performance.now();
256-
let logMessage = `Copilot: resolve(${context.documentContext.uri}:${context.documentContext.offset}):`;
257-
const timeBudgetFactor = await this.fetchTimeBudgetFactor(context);
301+
let logMessage = `Copilot: resolve(${context.documentContext.uri}: ${context.documentContext.offset}): `;
302+
const cppTimeBudgetMs = await this.fetchTimeBudgetMs(context);
258303
const maxCaretDistance = await this.fetchMaxDistanceToCaret(context);
304+
const maxSnippetCount = await this.fetchMaxSnippetCount(context);
305+
const maxSnippetLength = await this.fetchMaxSnippetLength(context);
259306
const telemetry = new CopilotCompletionContextTelemetry();
260307
let copilotCompletionContext: CopilotCompletionContextResult | undefined;
261308
let copilotCompletionContextKind: CopilotCompletionKind = CopilotCompletionKind.Unknown;
@@ -265,16 +312,12 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
265312
try {
266313
featureFlag = await this.getEnabledFeatureFlag(context);
267314
telemetry.addRequestMetadata(context.documentContext.uri, context.documentContext.offset,
268-
context.completionId, context.documentContext.languageId, { featureFlag, timeBudgetFactor, maxCaretDistance });
315+
context.completionId, context.documentContext.languageId, { featureFlag, timeBudgetMs: cppTimeBudgetMs, maxCaretDistance });
269316
if (featureFlag === undefined) { return []; }
270-
this.completionContextCancellation.cancel();
271-
this.completionContextCancellation = new vscode.CancellationTokenSource();
272317
const cacheEntry: CacheEntry | undefined = this.completionContextCache.get(docUri.toString());
273318
const defaultValue = cacheEntry?.[1];
274-
const computeSnippetsPromise = this.getCompletionContextWithCancellation(context, featureFlag,
275-
resolveStartTime, telemetry.fork(), this.completionContextCancellation.token);
276-
[copilotCompletionContext, copilotCompletionContextKind] = await this.waitForCompletionWithTimeoutAndCancellation(
277-
computeSnippetsPromise, defaultValue, context.timeBudget * timeBudgetFactor, copilotCancel);
319+
[copilotCompletionContext, copilotCompletionContextKind] = await this.resolveResultAndKind(context, featureFlag,
320+
telemetry.fork(), defaultValue, resolveStartTime, cppTimeBudgetMs, maxSnippetCount, maxSnippetLength, copilotCancel);
278321
// Fix up copilotCompletionContextKind accounting for stale-cache-hits.
279322
if (copilotCompletionContextKind === CopilotCompletionKind.GotFromCache &&
280323
copilotCompletionContext && cacheEntry) {
@@ -292,12 +335,12 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
292335
telemetry.addCopilotCanceled(duration);
293336
throw new CopilotCancellationError();
294337
}
295-
logMessage += ` (id:${copilotCompletionContext?.requestId}) `;
338+
logMessage += ` (id: ${copilotCompletionContext?.requestId})`;
296339
return [...copilotCompletionContext?.snippets ?? [], ...copilotCompletionContext?.traits ?? []] as SupportedContextItem[];
297340
} catch (e: any) {
298341
if (e instanceof CopilotCancellationError) {
299342
telemetry.addCopilotCanceled(CopilotCompletionContextProvider.getRoundedDuration(resolveStartTime));
300-
logMessage += ` (copilot cancellation) `;
343+
logMessage += ` (copilot cancellation)`;
301344
throw e;
302345
}
303346
if (e instanceof InternalCancellationError) {
@@ -312,12 +355,12 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
312355
throw e;
313356
} finally {
314357
const duration: number = CopilotCompletionContextProvider.getRoundedDuration(resolveStartTime);
315-
logMessage += `featureFlag:${featureFlag?.toString()},`;
358+
logMessage += `featureFlag:${featureFlag?.toString()}, `;
316359
if (copilotCompletionContext === undefined) {
317-
logMessage += ` result is undefined and no snippets provided (${copilotCompletionContextKind.toString()}), elapsed time:${duration}ms`;
360+
logMessage += ` result is undefined and no snippets provided(${copilotCompletionContextKind.toString()}), elapsed time:${duration} ms`;
318361
} else {
319-
logMessage += ` for ${docUri}:${docOffset} provided ${copilotCompletionContext.snippets.length} code-snippet(s) (${copilotCompletionContextKind.toString()}\
320-
${copilotCompletionContext?.areSnippetsMissing ? ", missing code-snippets" : ""}) and ${copilotCompletionContext.traits.length} trait(s), elapsed time:${duration}ms`;
362+
logMessage += ` for ${docUri}:${docOffset} provided ${copilotCompletionContext.snippets.length} code - snippet(s)(${copilotCompletionContextKind.toString()}\
363+
${copilotCompletionContext?.areSnippetsMissing ? ", missing code-snippets" : ""}) and ${copilotCompletionContext.traits.length} trait(s), elapsed time:${duration} ms`;
321364
}
322365
telemetry.addResponseMetadata(copilotCompletionContext?.areSnippetsMissing ?? true,
323366
copilotCompletionContext?.snippets.length, copilotCompletionContext?.traits.length,
@@ -349,7 +392,7 @@ ${copilotCompletionContext?.areSnippetsMissing ? ", missing code-snippets" : ""}
349392
console.debug("Failed to register the Copilot Context Provider.");
350393
properties["error"] = "Failed to register the Copilot Context Provider";
351394
if (e instanceof CopilotContextProviderException) {
352-
properties["error"] += `: ${e.message}`;
395+
properties["error"] += `: ${e.message} `;
353396
}
354397
} finally {
355398
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });

Extension/src/LanguageServer/copilotCompletionContextTelemetry.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,18 @@ export class CopilotCompletionContextTelemetry {
8888
}
8989

9090
public addRequestMetadata(uri: string, caretOffset: number, completionId: string,
91-
languageId: string, { featureFlag, timeBudgetFactor, maxCaretDistance }: {
92-
featureFlag?: CopilotCompletionContextFeatures;
93-
timeBudgetFactor?: number; maxCaretDistance?: number;
91+
languageId: string, { featureFlag, timeBudgetMs, maxCaretDistance, maxSnippetCount, maxSnippetLength }: {
92+
featureFlag?: CopilotCompletionContextFeatures; timeBudgetMs?: number; maxCaretDistance?: number;
93+
maxSnippetCount?: number; maxSnippetLength?: number;
9494
} = {}): void {
9595
this.addProperty('request.completionId', completionId);
9696
this.addProperty('request.languageId', languageId);
9797
this.addMetric('request.caretOffset', caretOffset);
9898
this.addProperty('request.featureFlag', featureFlag?.toString() ?? '<not-set>');
99-
if (timeBudgetFactor !== undefined) { this.addMetric('request.timeBudgetFactor', timeBudgetFactor); }
99+
if (timeBudgetMs !== undefined) { this.addMetric('request.timeBudgetMs', timeBudgetMs); }
100100
if (maxCaretDistance !== undefined) { this.addMetric('request.maxCaretDistance', maxCaretDistance); }
101+
if (maxSnippetCount !== undefined) { this.addMetric('request.maxSnippetCount', maxSnippetCount); }
102+
if (maxSnippetLength !== undefined) { this.addMetric('request.maxSnippetLength', maxSnippetLength); }
101103
}
102104

103105
public addCppStandardVersionMetadata(standardVersion: string, elapsedMs: number): void {

0 commit comments

Comments
 (0)