@@ -75,11 +75,19 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
75
75
private static readonly providerId = 'ms-vscode.cpptools' ;
76
76
private readonly completionContextCache : Map < string , CacheEntry > = new Map ( ) ;
77
77
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
81
84
private completionContextCancellation = new vscode . CancellationTokenSource ( ) ;
82
85
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' ;
83
91
84
92
constructor ( private readonly logger : Logger ) {
85
93
}
@@ -125,7 +133,8 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
125
133
// Get the completion context with a timeout and a cancellation token.
126
134
// The cancellationToken indicates that the value should not be returned nor cached.
127
135
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 ) :
129
138
Promise < CopilotCompletionContextResult | undefined > {
130
139
const documentUri = context . documentContext . uri ;
131
140
const caretOffset = context . documentContext . offset ;
@@ -143,7 +152,7 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
143
152
const getCompletionContextStartTime = performance . now ( ) ;
144
153
145
154
const copilotCompletionContext : CopilotCompletionContextResult =
146
- await client . getCompletionContext ( docUri , caretOffset , snippetsFeatureFlag , internalToken ) ;
155
+ await client . getCompletionContext ( docUri , caretOffset , snippetsFeatureFlag , maxSnippetCount , maxSnippetLength , internalToken ) ;
147
156
telemetry . addRequestId ( copilotCompletionContext . requestId ) ;
148
157
logMessage += `(id:${ copilotCompletionContext . requestId } ) (getClientFor elapsed:${ getClientForDuration } ms)` ;
149
158
if ( ! copilotCompletionContext . areSnippetsMissing ) {
@@ -187,38 +196,56 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
187
196
telemetry . send ( "cache" ) ;
188
197
}
189
198
}
190
- static readonly CppCodeSnippetsEnabledFeatures = 'CppCodeSnippetsEnabledFeatures' ;
191
- static readonly CppCodeSnippetsTimeBudgetFactor = 'CppCodeSnippetsTimeBudgetFactor' ;
192
- static readonly CppCodeSnippetsMaxDistanceToCaret = 'CppCodeSnippetsMaxDistanceToCaret' ;
193
199
194
- private async fetchTimeBudgetFactor ( context : ResolveRequest ) : Promise < number > {
200
+ private async fetchTimeBudgetMs ( context : ResolveRequest ) : Promise < number > {
195
201
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 ;
198
204
} 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 ;
201
207
}
202
208
}
203
209
204
210
private async fetchMaxDistanceToCaret ( context : ResolveRequest ) : Promise < number > {
205
211
try {
206
- const maxDistance = context . activeExperiments . get ( CopilotCompletionContextProvider . CppCodeSnippetsMaxDistanceToCaret ) ;
212
+ const maxDistance = context . activeExperiments . get ( CopilotCompletionContextProvider . CppContextProviderMaxDistanceToCaret ) ;
207
213
return isNumber ( maxDistance ) ? maxDistance : CopilotCompletionContextProvider . defaultMaxCaretDistance ;
208
214
} catch ( e ) {
209
- console . warn ( `fetchMaxDistanceToCaret(): error fetching ${ CopilotCompletionContextProvider . CppCodeSnippetsMaxDistanceToCaret } , using default: ` , e ) ;
215
+ console . warn ( `fetchMaxDistanceToCaret(): error fetching ${ CopilotCompletionContextProvider . CppContextProviderMaxDistanceToCaret } , using default: ` , e ) ;
210
216
return CopilotCompletionContextProvider . defaultMaxCaretDistance ;
211
217
}
212
218
}
213
219
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
+
214
240
private async getEnabledFeatureNames ( context : ResolveRequest ) : Promise < string [ ] | undefined > {
215
241
try {
216
- const enabledFeatureNames = new CppSettings ( ) . cppCodeSnippetsFeatureNames ?? context . activeExperiments . get ( CopilotCompletionContextProvider . CppCodeSnippetsEnabledFeatures ) ;
242
+ const enabledFeatureNames = new CppSettings ( ) . cppCodeSnippetsFeatureNames ??
243
+ context . activeExperiments . get ( CopilotCompletionContextProvider . CppContextProviderEnabledFeatures ) ;
217
244
if ( isString ( enabledFeatureNames ) ) {
218
245
return enabledFeatureNames . split ( ',' ) . map ( s => s . trim ( ) ) ;
219
246
}
220
247
} catch ( e ) {
221
- console . warn ( `getEnabledFeatures (): error fetching ${ CopilotCompletionContextProvider . CppCodeSnippetsEnabledFeatures } : ` , e ) ;
248
+ console . warn ( `getEnabledFeatureNames (): error fetching ${ CopilotCompletionContextProvider . CppContextProviderEnabledFeatures } : ` , e ) ;
222
249
}
223
250
return undefined ;
224
251
}
@@ -251,11 +278,31 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
251
278
this . completionContextCache . delete ( fileUri ) ;
252
279
}
253
280
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
+
254
299
public async resolve ( context : ResolveRequest , copilotCancel : vscode . CancellationToken ) : Promise < SupportedContextItem [ ] > {
255
300
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 ) ;
258
303
const maxCaretDistance = await this . fetchMaxDistanceToCaret ( context ) ;
304
+ const maxSnippetCount = await this . fetchMaxSnippetCount ( context ) ;
305
+ const maxSnippetLength = await this . fetchMaxSnippetLength ( context ) ;
259
306
const telemetry = new CopilotCompletionContextTelemetry ( ) ;
260
307
let copilotCompletionContext : CopilotCompletionContextResult | undefined ;
261
308
let copilotCompletionContextKind : CopilotCompletionKind = CopilotCompletionKind . Unknown ;
@@ -265,16 +312,12 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
265
312
try {
266
313
featureFlag = await this . getEnabledFeatureFlag ( context ) ;
267
314
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 } ) ;
269
316
if ( featureFlag === undefined ) { return [ ] ; }
270
- this . completionContextCancellation . cancel ( ) ;
271
- this . completionContextCancellation = new vscode . CancellationTokenSource ( ) ;
272
317
const cacheEntry : CacheEntry | undefined = this . completionContextCache . get ( docUri . toString ( ) ) ;
273
318
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 ) ;
278
321
// Fix up copilotCompletionContextKind accounting for stale-cache-hits.
279
322
if ( copilotCompletionContextKind === CopilotCompletionKind . GotFromCache &&
280
323
copilotCompletionContext && cacheEntry ) {
@@ -292,12 +335,12 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
292
335
telemetry . addCopilotCanceled ( duration ) ;
293
336
throw new CopilotCancellationError ( ) ;
294
337
}
295
- logMessage += ` (id:${ copilotCompletionContext ?. requestId } ) ` ;
338
+ logMessage += ` (id: ${ copilotCompletionContext ?. requestId } )` ;
296
339
return [ ...copilotCompletionContext ?. snippets ?? [ ] , ...copilotCompletionContext ?. traits ?? [ ] ] as SupportedContextItem [ ] ;
297
340
} catch ( e : any ) {
298
341
if ( e instanceof CopilotCancellationError ) {
299
342
telemetry . addCopilotCanceled ( CopilotCompletionContextProvider . getRoundedDuration ( resolveStartTime ) ) ;
300
- logMessage += ` (copilot cancellation) ` ;
343
+ logMessage += ` (copilot cancellation)` ;
301
344
throw e ;
302
345
}
303
346
if ( e instanceof InternalCancellationError ) {
@@ -312,12 +355,12 @@ response.uri:${copilotCompletionContext.sourceFileUri || "<not-set>"}:${copilotC
312
355
throw e ;
313
356
} finally {
314
357
const duration : number = CopilotCompletionContextProvider . getRoundedDuration ( resolveStartTime ) ;
315
- logMessage += `featureFlag:${ featureFlag ?. toString ( ) } ,` ;
358
+ logMessage += `featureFlag:${ featureFlag ?. toString ( ) } , ` ;
316
359
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` ;
318
361
} 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`;
321
364
}
322
365
telemetry . addResponseMetadata ( copilotCompletionContext ?. areSnippetsMissing ?? true ,
323
366
copilotCompletionContext ?. snippets . length , copilotCompletionContext ?. traits . length ,
@@ -349,7 +392,7 @@ ${copilotCompletionContext?.areSnippetsMissing ? ", missing code-snippets" : ""}
349
392
console . debug ( "Failed to register the Copilot Context Provider." ) ;
350
393
properties [ "error" ] = "Failed to register the Copilot Context Provider" ;
351
394
if ( e instanceof CopilotContextProviderException ) {
352
- properties [ "error" ] += `: ${ e . message } ` ;
395
+ properties [ "error" ] += `: ${ e . message } ` ;
353
396
}
354
397
} finally {
355
398
telemetry . logCopilotEvent ( registerCopilotContextProvider , { ...properties } ) ;
0 commit comments