Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ export class DefaultClient implements Client {
this.semanticTokensProviderDisposable = vscode.languages.registerDocumentSemanticTokensProvider(util.documentSelector, this.semanticTokensProvider, semanticTokensLegend);
}

this.copilotCompletionProvider = await CopilotCompletionContextProvider.Create();
this.copilotCompletionProvider = CopilotCompletionContextProvider.Create();
this.disposables.push(this.copilotCompletionProvider);

// Listen for messages from the language server.
Expand Down
112 changes: 74 additions & 38 deletions Extension/src/LanguageServer/copilotCompletionContextProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,9 @@ export class CopilotCompletionContextProvider implements ContextResolver<Support
return Math.round(performance.now() - startTime);
}

public static async Create() {
public static Create() {
const copilotCompletionProvider = new CopilotCompletionContextProvider(getOutputChannelLogger());
await copilotCompletionProvider.registerCopilotContextProvider();
copilotCompletionProvider.registerCopilotContextProvider();
return copilotCompletionProvider;
}

Expand Down Expand Up @@ -445,58 +445,94 @@ ${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}
}
}

public async registerCopilotContextProvider(): Promise<void> {
const properties: Record<string, string> = {};
public registerCopilotContextProvider(): void {
const registerCopilotContextProvider = 'registerCopilotContextProvider';
try {
const copilotApi = await getCopilotClientApi();
const copilotChatApi = await getCopilotChatApi();
if (!copilotApi && !copilotChatApi) { throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot is missing or inactive."); }
const contextProvider = {
id: CopilotCompletionContextProvider.providerId,
selector: CopilotCompletionContextProvider.defaultCppDocumentSelector,
resolver: this
};
type InstallSummary = { hasGetContextProviderAPI: boolean; hasAPI: boolean };
const installSummary: { client?: InstallSummary; chat?: InstallSummary } = {};
if (copilotApi) {
installSummary.client = await this.installContextProvider(copilotApi, contextProvider);
const contextProvider = {
id: CopilotCompletionContextProvider.providerId,
selector: CopilotCompletionContextProvider.defaultCppDocumentSelector,
resolver: this
};
type RegistrationResult = { message: string } | boolean;
const clientPromise: Promise<RegistrationResult> = getCopilotClientApi().then(async (api) => {
if (!api) {
throw new CopilotContextProviderException("getCopilotApi() returned null, Copilot client is missing or inactive.");
}
const disposable = await this.installContextProvider(api, contextProvider);
if (disposable) {
this.contextProviderDisposables = this.contextProviderDisposables ?? [];
this.contextProviderDisposables.push(disposable);
return true;
} else {
throw new CopilotContextProviderException("getContextProviderAPI() is not available in Copilot client.");
}
if (copilotChatApi) {
installSummary.chat = await this.installContextProvider(copilotChatApi, contextProvider);
}).catch((e) => {
console.debug("Failed to register the Copilot Context Provider with Copilot client.");
let message = "Failed to register the Copilot Context Provider with Copilot client";
if (e instanceof CopilotContextProviderException) {
message += `: ${e.message} `;
}
if (installSummary.client?.hasAPI || installSummary.chat?.hasAPI) {
properties["cppCodeSnippetsProviderRegistered"] = "true";
return { message };
});
const chatPromise: Promise<RegistrationResult> = getCopilotChatApi().then(async (api) => {
if (!api) {
throw new CopilotContextProviderException("getCopilotChatApi() returned null, Copilot Chat is missing or inactive.");
}
const disposable = await this.installContextProvider(api, contextProvider);
if (disposable) {
this.contextProviderDisposables = this.contextProviderDisposables ?? [];
this.contextProviderDisposables.push(disposable);
return true;
} else {
if (installSummary.client?.hasGetContextProviderAPI === false &&
installSummary.chat?.hasGetContextProviderAPI === false) {
throw new CopilotContextProviderException("getContextProviderAPI() is not available.");
} else {
throw new CopilotContextProviderException("getContextProviderAPI(v1) returned null.");
}
throw new CopilotContextProviderException("getContextProviderAPI() is not available in Copilot Chat.");
}
} catch (e) {
console.debug("Failed to register the Copilot Context Provider.");
properties["error"] = "Failed to register the Copilot Context Provider";
}).catch((e) => {
console.debug("Failed to register the Copilot Context Provider with Copilot Chat.");
let message = "Failed to register the Copilot Context Provider with Copilot Chat";
if (e instanceof CopilotContextProviderException) {
properties["error"] += `: ${e.message} `;
message += `: ${e.message} `;
}
} finally {
return { message };
});
// The client usually doesn't block. So test it first.
clientPromise.then((clientResult) => {
const properties: Record<string, string> = {};
if (isBoolean(clientResult) && clientResult) {
properties["cppCodeSnippetsProviderRegistered"] = "true";
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });
return;
}
return chatPromise.then((chatResult) => {
const properties: Record<string, string> = {};
if (isBoolean(chatResult) && chatResult) {
properties["cppCodeSnippetsProviderRegistered"] = "true";
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });
return;
} else if (!isBoolean(clientResult) && isString(clientResult.message)) {
properties["error"] = clientResult.message;
} else if (!isBoolean(chatResult) && isString(chatResult.message)) {
properties["error"] = chatResult.message;
} else {
properties["error"] = "Failed to register the Copilot Context Provider for unknown reason.";
}
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });
});
}).catch((e) => {
const properties: Record<string, string> = {};
properties["error"] = `Failed to register the Copilot Context Provider with exception: ${e}`;
telemetry.logCopilotEvent(registerCopilotContextProvider, { ...properties });
}
});
}

private async installContextProvider(copilotAPI: CopilotContextProviderAPI, contextProvider: ContextProvider<SupportedContextItem>): Promise<{ hasGetContextProviderAPI: boolean; hasAPI: boolean }> {
private async installContextProvider(copilotAPI: CopilotContextProviderAPI, contextProvider: ContextProvider<SupportedContextItem>): Promise<vscode.Disposable | undefined> {
const hasGetContextProviderAPI = typeof copilotAPI.getContextProviderAPI === 'function';
if (hasGetContextProviderAPI) {
const contextAPI = await copilotAPI.getContextProviderAPI("v1");
if (contextAPI) {
this.contextProviderDisposables = this.contextProviderDisposables ?? [];
this.contextProviderDisposables.push(contextAPI.registerContextProvider(contextProvider));
return contextAPI.registerContextProvider(contextProvider);
}
return { hasGetContextProviderAPI, hasAPI: contextAPI !== undefined };
return undefined;
} else {
return { hasGetContextProviderAPI: false, hasAPI: false };
return undefined;
}
}
}
9 changes: 1 addition & 8 deletions Extension/src/LanguageServer/copilotProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,7 @@ export async function getCopilotChatApi(): Promise<CopilotContextProviderAPI | u
let exports: CopilotChatApi | undefined;
if (!copilotExtension.isActive) {
try {
exports = await Promise.race([
copilotExtension.activate(),
new Promise<undefined>(resolve => {
setTimeout(() => {
resolve(undefined);
}, 3000);
})
]);
exports = await copilotExtension.activate();
} catch {
return undefined;
}
Expand Down