diff --git a/package.json b/package.json index 2c876d251..d06dabd7f 100644 --- a/package.json +++ b/package.json @@ -2126,8 +2126,7 @@ "command": "github.copilot.chat.manageModels", "title": "Manage Models...", "icon": "$(settings-gear)", - "category": "GitHub Copilot", - "enablement": "github.copilot.byokEnabled" + "category": "GitHub Copilot" }, { "command": "github.copilot.chat.debug.showElements", @@ -2915,8 +2914,7 @@ "menus": { "chat/modelPicker": [ { - "command": "github.copilot.chat.manageModels", - "when": "github.copilot.byokEnabled" + "command": "github.copilot.chat.manageModels" } ], "editor/title": [ diff --git a/src/extension/byok/common/byokProvider.ts b/src/extension/byok/common/byokProvider.ts index e287d4148..1f6a91773 100644 --- a/src/extension/byok/common/byokProvider.ts +++ b/src/extension/byok/common/byokProvider.ts @@ -130,8 +130,30 @@ export function resolveModelInfo(modelId: string, providerName: string, knownMod }; } +/** + * Determines if Bring Your Own Key (BYOK) functionality is enabled for the current user. + * + * BYOK availability rules: + * - GitHub Enterprise Server: Not available (cloud endpoints required) + * - All cloud Copilot plans (internal, individual, business, enterprise): Enabled + * + * NOTE: we previously gated Business/Enterprise tenants behind the "Editor Preview Features" org + * policy. That restriction has been removed. We instead surface an in-product disclaimer when a + * user opens the Manage Models UI to make it clear that externally configured (BYOK) models are + * not covered by Copilot model quality, data handling, or compliance guarantees. + * + * @param copilotToken The user's Copilot token (without the actual token value) + * @param capiClientService Service to check if running on GitHub Enterprise + * @returns true if BYOK should be enabled for this user + */ export function isBYOKEnabled(copilotToken: Omit, capiClientService: ICAPIClientService): boolean { const isGHE = capiClientService.dotcomAPIURL !== 'https://api.github.com'; - const byokAllowed = (copilotToken.isInternal || copilotToken.isIndividual) && !isGHE; - return byokAllowed; + + // Not available on GitHub Enterprise Server instances (cloud only) + if (isGHE) { + return false; + } + + // Enabled for all cloud Copilot users regardless of SKU or preview policy. + return true; } \ 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 a2784f9e8..305726e09 100644 --- a/src/extension/byok/vscode-node/byokContribution.ts +++ b/src/extension/byok/vscode-node/byokContribution.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, Disposable as VSCodeDisposable, window } from 'vscode'; +import { commands, Uri, Disposable as VSCodeDisposable, window } from 'vscode'; import { IAuthenticationService } from '../../../platform/authentication/common/authentication'; import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService'; import { ICAPIClientService } from '../../../platform/endpoint/common/capiClient'; @@ -31,6 +31,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution { private _registeredModelDisposables = new Map(); private _byokUIService!: BYOKUIService; // Set in authChange, so ok to ! private readonly _byokStorageService: IBYOKStorageService; + private _byokDisclaimerShown = false; // Session‑scoped disclaimer gate constructor( @IFetcherService private readonly _fetcherService: IFetcherService, @@ -126,6 +127,25 @@ export class BYOKContrib extends Disposable implements IExtensionContribution { } private async registerModelCommand() { + // One‑time disclaimer: make it clear that BYOK models are external and not covered by Copilot guarantees. + if (!this._byokDisclaimerShown) { + this._byokDisclaimerShown = true; // ensure we only show once per session + const detail = 'Models you configure here are provided by external services that you choose. Your prompts and code may be sent to those services and are subject to their terms. GitHub Copilot\'s data handling, compliance, and quality guarantees do NOT apply to BYOK models.'; + const learnMore = 'Learn More'; + const continueLabel = 'Continue'; + const selection = await window.showWarningMessage('Bring Your Own Model', { modal: true, detail }, continueLabel, learnMore); + if (selection === learnMore) { + try { + await commands.executeCommand('vscode.open', Uri.parse('https://code.visualstudio.com/docs/copilot/language-models')); + } catch (err) { + this._logService.logger.error('Failed to open BYOK docs', err); + } + } + if (selection !== continueLabel) { + return; // user cancelled + } + } + // Start the model management flow - this will handle both provider selection and model selection const result = await this._byokUIService.startModelManagementFlow(); if (!result) { diff --git a/src/extension/byok/vscode-node/byokUIService.ts b/src/extension/byok/vscode-node/byokUIService.ts index cdb3ffea0..45a8fc26c 100644 --- a/src/extension/byok/vscode-node/byokUIService.ts +++ b/src/extension/byok/vscode-node/byokUIService.ts @@ -170,7 +170,7 @@ function createQuickPickWithBackButton( async function createErrorModal(errorMessage: string, currentStep: ConfigurationStep): Promise { - const result = await window.showErrorMessage('Unexpected Error - Manage Models - Preview', { detail: errorMessage, modal: true }, 'Retry', 'Go Back'); + const result = await window.showErrorMessage('Unexpected Error - Manage Models', { detail: errorMessage, modal: true }, 'Retry', 'Go Back'); if (result === 'Retry') { return { nextStep: currentStep }; } else if (result === 'Go Back') { @@ -322,7 +322,7 @@ export class BYOKUIService { // Use manual quick pick creation for item button handling const quickPick = window.createQuickPick(); - quickPick.title = 'Manage Models - Preview'; + quickPick.title = 'Manage Models'; quickPick.ignoreFocusOut = false; quickPick.placeholder = 'Select a provider'; quickPick.items = quickPickItems; @@ -408,7 +408,7 @@ export class BYOKUIService { const quickPick = window.createQuickPick(); quickPick.busy = true; quickPick.buttons = [QuickInputButtons.Back]; - quickPick.title = `Manage ${state.providerName} Models - Preview`; + quickPick.title = `Manage ${state.providerName} Models`; quickPick.ignoreFocusOut = true; quickPick.placeholder = `Fetching models...`; quickPick.canSelectMany = true; @@ -758,7 +758,7 @@ export class BYOKUIService { private async promptForAPIKey(contextName: string, reconfigure: boolean = false): Promise { const prompt = reconfigure ? `Enter new ${contextName} API Key or leave blank to delete saved key` : `Enter ${contextName} API Key`; - const title = reconfigure ? `Reconfigure ${contextName} API Key - Preview` : `Enter ${contextName} API Key - Preview`; + const title = reconfigure ? `Reconfigure ${contextName} API Key` : `Enter ${contextName} API Key`; const result = await createInputBoxWithBackButton({ prompt: prompt,