diff --git a/extensions/copilot/chat-lib/test/getInlineCompletions.spec.ts b/extensions/copilot/chat-lib/test/getInlineCompletions.spec.ts index 05b0fc66877..1902761460e 100644 --- a/extensions/copilot/chat-lib/test/getInlineCompletions.spec.ts +++ b/extensions/copilot/chat-lib/test/getInlineCompletions.spec.ts @@ -28,6 +28,8 @@ import { ChatRequest } from '../src/_internal/vscodeTypes'; import { createInlineCompletionsProvider, IActionItem, IAuthenticationService, ICompletionsStatusChangedEvent, ICompletionsTextDocumentManager, IEndpointProvider, ILogTarget, ITelemetrySender, LogLevel } from '../src/main'; class TestFetcher implements IFetcher { + private _fetched = new Map(); + constructor(private readonly responses: Record) { } getUserAgentLibrary(): string { @@ -36,6 +38,7 @@ class TestFetcher implements IFetcher { async fetch(url: string, options: FetchOptions): Promise { const uri = URI.parse(url); + this._markFetched(uri.path); const responseText = this.responses[uri.path]; const headers = new class implements IHeaders { @@ -59,6 +62,15 @@ class TestFetcher implements IFetcher { ); } + private _markFetched(urlPath: string): void { + const count = this.fetchCount(urlPath); + this._fetched.set(urlPath, count + 1); + } + + fetchCount(urlPath: string): number { + return this._fetched.get(urlPath) || 0; + } + fetchWithPagination(baseUrl: string, options: PaginationOptions): Promise { throw new Error('Method not implemented.'); } @@ -195,9 +207,14 @@ class NullLogTarget implements ILogTarget { } describe('getInlineCompletions', () => { - it('should return completions for a document and position', async () => { - const provider = createInlineCompletionsProvider({ - fetcher: new TestFetcher({ '/v1/engines/gpt-41-copilot/completions': await readFile(join(__dirname, 'getInlineCompletions.reply.txt'), 'utf8') }), + const completionsPath = '/v1/engines/gpt-41-copilot/completions'; + let fetcher: TestFetcher; + + async function getCompletionsProvider() { + fetcher = new TestFetcher({ [completionsPath]: await readFile(join(__dirname, 'getInlineCompletions.reply.txt'), 'utf8') }); + + return createInlineCompletionsProvider({ + fetcher, authService: new TestAuthService(), telemetrySender: new TestTelemetrySender(), logTarget: new NullLogTarget(), @@ -221,6 +238,10 @@ describe('getInlineCompletions', () => { }, endpointProvider: new TestEndpointProvider(), }); + } + + it('should return completions for a document and position', async () => { + const provider = await getCompletionsProvider(); const doc = createTextDocument('file:///test.txt', 'javascript', 1, 'function main() {\n\n}\n'); const result = await provider.getInlineCompletions(doc, { line: 1, character: 0 }); @@ -230,4 +251,18 @@ describe('getInlineCompletions', () => { expect(result[0].resultType).toBe(ResultType.Async); expect(result[0].displayText).toBe(' console.log("Hello, World!");'); }); + + it('makes any pending speculative requests when a completion is shown', async () => { + const provider = await getCompletionsProvider(); + const doc = createTextDocument('file:///test.txt', 'javascript', 1, 'function main() {\n\n}\n'); + + const result = await provider.getInlineCompletions(doc, { line: 1, character: 0 }); + + assert(result); + expect(result.length).toBe(1); + + await provider.inlineCompletionShown(result[0].clientCompletionId); + + expect(fetcher.fetchCount(completionsPath)).toBe(2); + }); }); diff --git a/extensions/copilot/src/lib/node/chatLibMain.ts b/extensions/copilot/src/lib/node/chatLibMain.ts index 207ad03c235..6803ae88c41 100644 --- a/extensions/copilot/src/lib/node/chatLibMain.ts +++ b/extensions/copilot/src/lib/node/chatLibMain.ts @@ -628,6 +628,7 @@ export type IGetInlineCompletionsOptions = Exclude, export interface IInlineCompletionsProvider { updateTreatmentVariables(variables: Record): void; getInlineCompletions(textDocument: ITextDocument, position: Position, token?: CancellationToken, options?: IGetInlineCompletionsOptions): Promise; + inlineCompletionShown(completionId: string): Promise; dispose(): void; } @@ -643,6 +644,7 @@ class InlineCompletionsProvider extends Disposable implements IInlineCompletions constructor( @IInstantiationService private _insta: IInstantiationService, @IExperimentationService private readonly _expService: IExperimentationService, + @ICompletionsSpeculativeRequestCache private readonly _speculativeRequestCache: ICompletionsSpeculativeRequestCache, ) { super(); @@ -659,6 +661,10 @@ class InlineCompletionsProvider extends Disposable implements IInlineCompletions async getInlineCompletions(textDocument: ITextDocument, position: Position, token?: CancellationToken, options?: IGetInlineCompletionsOptions): Promise { return await this.ghostText.getInlineCompletions(textDocument, position, token, options); } + + async inlineCompletionShown(completionId: string): Promise { + return await this._speculativeRequestCache.request(completionId); + } } class UnwrappingTelemetrySender implements ITelemetrySender {