Skip to content

Commit b0f5f46

Browse files
committed
Register context provider API with copilot chat as well.
1 parent 72f7192 commit b0f5f46

File tree

2 files changed

+67
-14
lines changed

2 files changed

+67
-14
lines changed

Extension/src/LanguageServer/copilotCompletionContextProvider.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) Microsoft Corporation. All Rights Reserved.
33
* See 'LICENSE' in the project root for license information.
44
* ------------------------------------------------------------------------------------------ */
5-
import { ContextResolver, ResolveRequest, SupportedContextItem } from '@github/copilot-language-server';
5+
import { ContextResolver, ResolveRequest, SupportedContextItem, type ContextProvider } from '@github/copilot-language-server';
66
import { randomUUID } from 'crypto';
77
import * as vscode from 'vscode';
88
import { DocumentSelector } from 'vscode-languageserver-protocol';
@@ -11,7 +11,7 @@ import { getOutputChannelLogger, Logger } from '../logger';
1111
import * as telemetry from '../telemetry';
1212
import { CopilotCompletionContextResult } from './client';
1313
import { CopilotCompletionContextTelemetry } from './copilotCompletionContextTelemetry';
14-
import { getCopilotApi } from './copilotProviders';
14+
import { getCopilotApi, getCopilotChatApi, type CopilotChatApi } from './copilotProviders';
1515
import { clients } from './extension';
1616
import { CppSettings } from './settings';
1717

@@ -83,7 +83,7 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
8383
private static readonly defaultMaxSnippetLength = 3 * 1024;
8484
private static readonly defaultDoAggregateSnippets = true;
8585
private completionContextCancellation = new vscode.CancellationTokenSource();
86-
private contextProviderDisposable: vscode.Disposable | undefined;
86+
private contextProviderDisposables: vscode.Disposable[] | undefined;
8787
static readonly CppContextProviderEnabledFeatures = 'enabledFeatures';
8888
static readonly CppContextProviderTimeBudgetMs = 'timeBudgetMs';
8989
static readonly CppContextProviderMaxSnippetCount = 'maxSnippetCount';
@@ -312,7 +312,12 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
312312

313313
public dispose(): void {
314314
this.completionContextCancellation.cancel();
315-
this.contextProviderDisposable?.dispose();
315+
if (this.contextProviderDisposables) {
316+
for (const disposable of this.contextProviderDisposables) {
317+
disposable.dispose();
318+
}
319+
this.contextProviderDisposables = undefined;
320+
}
316321
}
317322

318323
public removeFile(fileUri: string): void {
@@ -445,17 +450,31 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}
445450
const registerCopilotContextProvider = 'registerCopilotContextProvider';
446451
try {
447452
const copilotApi = await getCopilotApi();
448-
if (!copilotApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); }
449-
const hasGetContextProviderAPI = "getContextProviderAPI" in copilotApi;
450-
if (!hasGetContextProviderAPI) { throw new CopilotContextProviderException("getContextProviderAPI() is not available."); }
451-
const contextAPI = await copilotApi.getContextProviderAPI("v1");
452-
if (!contextAPI) { throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null."); }
453-
this.contextProviderDisposable = contextAPI.registerContextProvider({
453+
const copilotChatApi = await getCopilotChatApi();
454+
if (!copilotApi && !copilotChatApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); }
455+
const contextProvider = {
454456
id: CopilotCompletionContextProvider.providerId,
455457
selector: CopilotCompletionContextProvider.defaultCppDocumentSelector,
456458
resolver: this
457-
});
458-
properties["cppCodeSnippetsProviderRegistered"] = "true";
459+
};
460+
type InstallSummary = { hasGetContextProviderAPI: boolean; hasAPI: boolean };
461+
const installSummary: { client?: InstallSummary; chat?: InstallSummary } = {};
462+
if (copilotApi) {
463+
installSummary.client = await this.installContextProvider(copilotApi, contextProvider);
464+
}
465+
if (copilotChatApi) {
466+
installSummary.chat = await this.installContextProvider(copilotChatApi, contextProvider);
467+
}
468+
if (installSummary.client?.hasAPI || installSummary.chat?.hasAPI) {
469+
properties["cppCodeSnippetsProviderRegistered"] = "true";
470+
} else {
471+
if (installSummary.client?.hasGetContextProviderAPI === false &&
472+
installSummary.chat?.hasGetContextProviderAPI === false) {
473+
throw new CopilotContextProviderException("getContextProviderAPI() is not available.");
474+
} else {
475+
throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null.");
476+
}
477+
}
459478
} catch (e) {
460479
console.debug("Failed to register the Copilot Context Provider.");
461480
properties["error"] = "Failed to register the Copilot Context Provider";
@@ -466,4 +485,18 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}
466485
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });
467486
}
468487
}
488+
489+
private async installContextProvider(copilotAPI: CopilotChatApi, contextProvider: ContextProvider<SupportedContextItem>): Promise<{ hasGetContextProviderAPI: boolean; hasAPI: boolean }> {
490+
const hasGetContextProviderAPI = "getContextProviderAPI" in copilotAPI;
491+
if (hasGetContextProviderAPI) {
492+
const contextAPI = await copilotAPI.getContextProviderAPI("v1");
493+
if (contextAPI) {
494+
this.contextProviderDisposables = this.contextProviderDisposables ?? [];
495+
this.contextProviderDisposables.push(contextAPI.registerContextProvider(contextProvider));
496+
}
497+
return { hasGetContextProviderAPI, hasAPI: contextAPI !== undefined };
498+
} else {
499+
return { hasGetContextProviderAPI: false, hasAPI: false };
500+
}
501+
}
469502
}

Extension/src/LanguageServer/copilotProviders.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ export interface CopilotTrait {
2424
promptTextOverride?: string;
2525
}
2626

27-
export interface CopilotApi {
27+
export interface CopilotChatApi {
28+
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
29+
}
30+
31+
export interface CopilotApi extends CopilotChatApi {
2832
registerRelatedFilesProvider(
2933
providerId: { extensionId: string; languageId: string },
3034
callback: (
@@ -33,7 +37,6 @@ export interface CopilotApi {
3337
cancellationToken: vscode.CancellationToken
3438
) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] } | undefined>
3539
): Disposable;
36-
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
3740
}
3841

3942
export async function registerRelatedFilesProvider(): Promise<void> {
@@ -145,3 +148,20 @@ export async function getCopilotApi(): Promise<CopilotApi | undefined> {
145148
return copilotExtension.exports;
146149
}
147150
}
151+
152+
export async function getCopilotChatApi(): Promise<CopilotChatApi | undefined> {
153+
const copilotExtension = vscode.extensions.getExtension<CopilotChatApi>('github.copilot-chat');
154+
if (!copilotExtension) {
155+
return undefined;
156+
}
157+
158+
if (!copilotExtension.isActive) {
159+
try {
160+
return await copilotExtension.activate();
161+
} catch {
162+
return undefined;
163+
}
164+
} else {
165+
return copilotExtension.exports;
166+
}
167+
}

0 commit comments

Comments
 (0)