Skip to content

Commit eb6d6ab

Browse files
authored
Register the CPP context provider with Copilot Chat as well. (#13877)
* Register context provider API with copilot chat as well.
1 parent 72f7192 commit eb6d6ab

File tree

2 files changed

+81
-17
lines changed

2 files changed

+81
-17
lines changed

Extension/src/LanguageServer/copilotCompletionContextProvider.ts

Lines changed: 46 additions & 13 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 { getCopilotChatApi, getCopilotClientApi, type CopilotContextProviderAPI } 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 {
@@ -444,18 +449,32 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}
444449
const properties: Record<string, string> = {};
445450
const registerCopilotContextProvider = 'registerCopilotContextProvider';
446451
try {
447-
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({
452+
const copilotApi = await getCopilotClientApi();
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: CopilotContextProviderAPI, contextProvider: ContextProvider<SupportedContextItem>): Promise<{ hasGetContextProviderAPI: boolean; hasAPI: boolean }> {
490+
const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function';
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: 35 additions & 4 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 CopilotContextProviderAPI {
28+
getContextProviderAPI(version: string): Promise<ContextProviderApiV1 | undefined>;
29+
}
30+
31+
export interface CopilotApi extends CopilotContextProviderAPI {
2832
registerRelatedFilesProvider(
2933
providerId: { extensionId: string; languageId: string },
3034
callback: (
@@ -33,11 +37,10 @@ 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> {
40-
const api = await getCopilotApi();
43+
const api = await getCopilotClientApi();
4144
if (util.extensionContext && api) {
4245
try {
4346
for (const languageId of ['c', 'cpp', 'cuda-cpp']) {
@@ -129,7 +132,7 @@ async function getIncludes(uri: vscode.Uri, maxDepth: number): Promise<GetInclud
129132
return includes;
130133
}
131134

132-
export async function getCopilotApi(): Promise<CopilotApi | undefined> {
135+
export async function getCopilotClientApi(): Promise<CopilotApi | undefined> {
133136
const copilotExtension = vscode.extensions.getExtension<CopilotApi>('github.copilot');
134137
if (!copilotExtension) {
135138
return undefined;
@@ -145,3 +148,31 @@ export async function getCopilotApi(): Promise<CopilotApi | undefined> {
145148
return copilotExtension.exports;
146149
}
147150
}
151+
152+
export async function getCopilotChatApi(): Promise<CopilotContextProviderAPI | undefined> {
153+
type CopilotChatApi = { getAPI?(version: number): CopilotContextProviderAPI | undefined };
154+
const copilotExtension = vscode.extensions.getExtension<CopilotChatApi>('github.copilot-chat');
155+
if (!copilotExtension) {
156+
return undefined;
157+
}
158+
159+
let exports: CopilotChatApi | undefined;
160+
if (!copilotExtension.isActive) {
161+
try {
162+
exports = await copilotExtension.activate();
163+
} catch {
164+
return undefined;
165+
}
166+
} else {
167+
exports = copilotExtension.exports;
168+
}
169+
if (!exports || typeof exports.getAPI !== 'function') {
170+
return undefined;
171+
}
172+
const result = exports.getAPI(1);
173+
return result;
174+
}
175+
176+
interface Disposable {
177+
dispose(): void;
178+
}

0 commit comments

Comments
 (0)