diff --git a/package.json b/package.json index e14d5dc29..c29d6f7d3 100644 --- a/package.json +++ b/package.json @@ -1576,6 +1576,10 @@ "vendor": "openrouter", "displayName": "OpenRouter", "managementCommand": "github.copilot.chat.manageBYOK" + }, + { + "vendor": "github", + "displayName": "GitHub Models" } ], "interactiveSession": [ @@ -3708,4 +3712,4 @@ "string_decoder": "npm:string_decoder@1.2.0", "node-gyp": "npm:node-gyp@10.3.1" } -} \ No newline at end of file +} diff --git a/src/extension/byok/vscode-node/byokContribution.ts b/src/extension/byok/vscode-node/byokContribution.ts index b9358c42b..dfccb8ea6 100644 --- a/src/extension/byok/vscode-node/byokContribution.ts +++ b/src/extension/byok/vscode-node/byokContribution.ts @@ -17,6 +17,7 @@ import { AnthropicLMProvider } from './anthropicProvider'; import { AzureBYOKModelProvider } from './azureProvider'; import { BYOKStorageService, IBYOKStorageService } from './byokStorageService'; import { GeminiBYOKLMProvider } from './geminiProvider'; +import { GitHubProvider } from './githubProvider'; import { GroqBYOKLMProvider } from './groqProvider'; import { OllamaLMProvider } from './ollamaProvider'; import { OAIBYOKLMProvider } from './openAIProvider'; @@ -60,6 +61,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution { this._providers.set(OllamaLMProvider.providerName.toLowerCase(), instantiationService.createInstance(OllamaLMProvider, this._configurationService.getConfig(ConfigKey.OllamaEndpoint), this._byokStorageService)); this._providers.set(AnthropicLMProvider.providerName.toLowerCase(), instantiationService.createInstance(AnthropicLMProvider, knownModels[AnthropicLMProvider.providerName], this._byokStorageService)); this._providers.set(GroqBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(GroqBYOKLMProvider, knownModels[GroqBYOKLMProvider.providerName], this._byokStorageService)); + this._providers.set(GitHubProvider.providerName.toLowerCase(), instantiationService.createInstance(GitHubProvider, this._byokStorageService)); this._providers.set(GeminiBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(GeminiBYOKLMProvider, knownModels[GeminiBYOKLMProvider.providerName], this._byokStorageService)); this._providers.set(OAIBYOKLMProvider.providerName.toLowerCase(), instantiationService.createInstance(OAIBYOKLMProvider, knownModels[OAIBYOKLMProvider.providerName], this._byokStorageService)); this._providers.set(OpenRouterLMProvider.providerName.toLowerCase(), instantiationService.createInstance(OpenRouterLMProvider, this._byokStorageService)); diff --git a/src/extension/byok/vscode-node/githubProvider.ts b/src/extension/byok/vscode-node/githubProvider.ts new file mode 100644 index 000000000..4a3a53f78 --- /dev/null +++ b/src/extension/byok/vscode-node/githubProvider.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { authentication } from 'vscode'; +import { ILogService } from '../../../platform/log/common/logService'; +import { IFetcherService } from '../../../platform/networking/common/fetcherService'; +import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation'; +import { BYOKAuthType, BYOKKnownModels } from '../common/byokProvider'; +import { BaseOpenAICompatibleLMProvider } from './baseOpenAICompatibleProvider'; +import { IBYOKStorageService } from './byokStorageService'; + +export class GitHubProvider extends BaseOpenAICompatibleLMProvider { + public static readonly providerName = 'GitHub'; + constructor( + byokStorageService: IBYOKStorageService, + @IFetcherService _fetcherService: IFetcherService, + @ILogService _logService: ILogService, + @IInstantiationService _instantiationService: IInstantiationService, + ) { + super( + BYOKAuthType.GlobalApiKey, + GitHubProvider.providerName, + 'https://models.github.ai/inference', + undefined, + byokStorageService, + _fetcherService, + _logService, + _instantiationService + ); + } + + /** + * Gets the GitHub access token using VS Code's authentication API + * @returns Promise GitHub access token if available + */ + private async getGitHubToken(): Promise { + const session = await authentication.getSession( + 'github', + ['repo', 'user:email'], + { createIfNone: true } + ); + return session?.accessToken; + } + + protected override async getAllModels(): Promise { + try { + const githubToken = await this.getGitHubToken(); + if (!githubToken) { + throw new Error('GitHub token is not available. Cannot fetch models.'); + } + const response = await this._fetcherService.fetch('https://models.github.ai/catalog/models', { + method: 'GET', + headers: { + 'Accept': 'application/vnd.github+json', + 'Authorization': `Bearer ${githubToken}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }); + + if (!response.ok) { + throw new Error(`Failed to fetch GitHub models: ${response.statusText}`); + } + + const models: any = await response.json(); + const knownModels: BYOKKnownModels = {}; + for (const model of models) { + knownModels[model.id] = { + name: model.name, + toolCalling: model.capabilities?.includes('tool-calling') ?? false, + vision: model.supported_input_modalities?.includes('image') ?? false, + maxInputTokens: model.limits?.max_input_tokens ?? 4096, + maxOutputTokens: model.limits?.max_output_tokens ?? 4096 + }; + } + this._knownModels = knownModels; + return knownModels; + } catch (error) { + this._logService.error('Error fetching available GitHub models:', error); + throw error; + } + } +} \ No newline at end of file