diff --git a/package-lock.json b/package-lock.json index 1e413f9388..000ec78a6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "42.42.42-placeholder", "license": "SEE LICENSE IN RuntimeLicenses/license.txt", "dependencies": { + "@github/copilot-language-server": "1.256.0", "@microsoft/servicehub-framework": "4.2.99-beta", "@octokit/rest": "^20.0.1", "@types/cross-spawn": "6.0.2", @@ -1122,6 +1123,42 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@github/copilot-language-server": { + "version": "1.256.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@github/copilot-language-server/-/copilot-language-server-1.256.0.tgz", + "integrity": "sha1-2tTKwjtIEoG7og5zrWdT6zfVk48=", + "dependencies": { + "vscode-languageserver-protocol": "^3.17.5" + }, + "bin": { + "copilot-language-server": "dist/language-server.js" + } + }, + "node_modules/@github/copilot-language-server/node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha1-9D36NftR52PRfNlNzKDJRY81q/k=", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@github/copilot-language-server/node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha1-hkqLjzkINVcvThO9n4MT0OOsS+o=", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/@github/copilot-language-server/node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha1-MnNnbwzy6rQLP0TQhay7fwijnYo=", + "license": "MIT" + }, "node_modules/@gulpjs/messages": { "version": "1.1.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@gulpjs/messages/-/messages-1.1.0.tgz", @@ -16615,6 +16652,35 @@ "integrity": "sha1-pUF66EJ4c/HdCLcLNXS0U+Z7X38=", "dev": true }, + "@github/copilot-language-server": { + "version": "1.256.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@github/copilot-language-server/-/copilot-language-server-1.256.0.tgz", + "integrity": "sha1-2tTKwjtIEoG7og5zrWdT6zfVk48=", + "requires": { + "vscode-languageserver-protocol": "^3.17.5" + }, + "dependencies": { + "vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha1-9D36NftR52PRfNlNzKDJRY81q/k=" + }, + "vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha1-hkqLjzkINVcvThO9n4MT0OOsS+o=", + "requires": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha1-MnNnbwzy6rQLP0TQhay7fwijnYo=" + } + } + }, "@gulpjs/messages": { "version": "1.1.0", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@gulpjs/messages/-/messages-1.1.0.tgz", diff --git a/package.json b/package.json index eda5e2335a..70de19de67 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,8 @@ "extensionDependencies": [ "ms-dotnettools.vscode-dotnet-runtime" ], - "dependencies": { + "dependencies": { + "@github/copilot-language-server": "1.256.0", "@microsoft/servicehub-framework": "4.2.99-beta", "@octokit/rest": "^20.0.1", "@types/cross-spawn": "6.0.2", diff --git a/src/lsptoolshost/copilot.ts b/src/lsptoolshost/copilot.ts index 4915606094..cf4a341ee3 100644 --- a/src/lsptoolshost/copilot.ts +++ b/src/lsptoolshost/copilot.ts @@ -2,13 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - +import { CodeSnippet, ContextProviderApiV1 } from '@github/copilot-language-server'; import * as vscode from 'vscode'; import { CSharpExtensionId } from '../constants/csharpExtensionId'; import { CopilotRelatedDocumentsReport, CopilotRelatedDocumentsRequest } from './roslynProtocol'; import { RoslynLanguageServer } from './roslynLanguageServer'; import { UriConverter } from './uriConverter'; import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; +import { getCSharpDevKit } from '../utils/getCSharpDevKit'; interface CopilotTrait { name: string; @@ -17,7 +18,7 @@ interface CopilotTrait { promptTextOverride?: string; } -interface CopilotRelatedFilesProviderRegistration { +interface CopilotAPIs { registerRelatedFilesProvider( providerId: { extensionId: string; languageId: string }, callback: ( @@ -26,74 +27,129 @@ interface CopilotRelatedFilesProviderRegistration { cancellationToken?: vscode.CancellationToken ) => Promise<{ entries: vscode.Uri[]; traits?: CopilotTrait[] }> ): vscode.Disposable; + getContextProviderAPI(version: string): Promise; } -export function registerCopilotExtension(languageServer: RoslynLanguageServer, channel: vscode.LogOutputChannel) { +async function getCopilotAPIsAsync(): Promise { const ext = vscode.extensions.getExtension('github.copilot'); if (!ext) { - channel.debug('GitHub Copilot extension not installed. Skip registeration of C# related files provider.'); - return; + return undefined; } - ext.activate().then(() => { - const relatedAPI = ext.exports as CopilotRelatedFilesProviderRegistration | undefined; - if (!relatedAPI) { - channel.debug( - 'Incompatible GitHub Copilot extension installed. Skip registeration of C# related files provider.' - ); - return; - } - channel.debug('registration of C# related files provider for GitHub Copilot extension succeeded.'); + if (!ext.isActive) { + try { + return await ext.activate(); + } catch { + return undefined; + } + } else { + return ext.exports as CopilotAPIs | undefined; + } +} - const id = { - extensionId: CSharpExtensionId, - languageId: 'csharp', - }; +function registerCopilotRelatedFilesProvider( + languageServer: RoslynLanguageServer, + copilotAPIs: CopilotAPIs, + channel: vscode.LogOutputChannel +): CopilotAPIs { + const id = { + extensionId: CSharpExtensionId, + languageId: 'csharp', + }; - relatedAPI.registerRelatedFilesProvider(id, async (uri, _, token) => { - const buildResult = ( - activeDocumentUri: vscode.Uri, - reports: CopilotRelatedDocumentsReport[], - builder: vscode.Uri[] - ) => { - if (reports) { - for (const report of reports) { - if (report._vs_file_paths) { - for (const filePath of report._vs_file_paths) { - // The Roslyn related document service would return the active document as related file to itself - // if the code contains reference to the types defined in the same document. Skip it so the active file - // won't be used as additonal context. - const relatedUri = vscode.Uri.file(filePath); - if (relatedUri.fsPath !== activeDocumentUri.fsPath) { - builder.push(relatedUri); - } + copilotAPIs.registerRelatedFilesProvider(id, async (uri, _, token) => { + const buildResult = ( + activeDocumentUri: vscode.Uri, + reports: CopilotRelatedDocumentsReport[], + builder: vscode.Uri[] + ) => { + if (reports) { + for (const report of reports) { + if (report._vs_file_paths) { + for (const filePath of report._vs_file_paths) { + // The Roslyn related document service would return the active document as related file to itself + // if the code contains reference to the types defined in the same document. Skip it so the active file + // won't be used as additonal context. + const relatedUri = vscode.Uri.file(filePath); + if (relatedUri.fsPath !== activeDocumentUri.fsPath) { + builder.push(relatedUri); } } } } - }; - const relatedFiles: vscode.Uri[] = []; - const uriString = UriConverter.serialize(uri); - const textDocument = TextDocumentIdentifier.create(uriString); - try { - await languageServer.sendRequestWithProgress( - CopilotRelatedDocumentsRequest.type, - { - _vs_textDocument: textDocument, - position: { - line: 0, - character: 0, - }, + } + }; + const relatedFiles: vscode.Uri[] = []; + const uriString = UriConverter.serialize(uri); + const textDocument = TextDocumentIdentifier.create(uriString); + try { + await languageServer.sendRequestWithProgress( + CopilotRelatedDocumentsRequest.type, + { + _vs_textDocument: textDocument, + position: { + line: 0, + character: 0, }, - async (r) => buildResult(uri, r, relatedFiles), - token + }, + async (r) => buildResult(uri, r, relatedFiles), + token + ); + } catch (e) { + if (e instanceof Error) { + channel.appendLine(e.message); + } + } + return { entries: relatedFiles }; + }); + + channel.debug('registration of C# related files provider for GitHub Copilot extension succeeded.'); + return copilotAPIs; +} + +async function registerCopilotContextProvider( + languageServer: RoslynLanguageServer, + copilotAPIs: CopilotAPIs, + channel: vscode.LogOutputChannel +) { + // Only register the context provider if the C# DevKit extension is available. + const csharpDevkitExtension = getCSharpDevKit(); + if (!csharpDevkitExtension) { + return; + } + + const contextAPI = await copilotAPIs.getContextProviderAPI('v1'); + if (!contextAPI) { + channel.debug('Failed to get context provider API from GitHub Copilot extension.'); + return; + } + + contextAPI.registerContextProvider({ + id: 'csharpContextProvider', + selector: [{ language: 'csharp' }], + resolver: { + resolve: async (request, token) => { + return [{ uri: 'testUri', value: 'testValue', additionalUris: [] }]; + }, + }, + }); + + channel.debug('registration of C# context provider for GitHub Copilot extension succeeded.'); +} + +export function registerCopilotExtension(languageServer: RoslynLanguageServer, channel: vscode.LogOutputChannel) { + getCopilotAPIsAsync() + .then(async (copilotAPIs) => { + if (!copilotAPIs) { + channel.debug( + 'Failed activating GitHub Copilot extension. Skip registeration of C# Copilot providers.' ); - } catch (e) { - if (e instanceof Error) { - channel.appendLine(e.message); - } + return; } - return { entries: relatedFiles }; + registerCopilotRelatedFilesProvider(languageServer, copilotAPIs, channel); + await registerCopilotContextProvider(languageServer, copilotAPIs, channel); + }) + .catch((error) => { + channel.debug('Failed registering C# Copilot providers. Error: ' + error); }); - }); }