Skip to content

Allow all SKUs to use BYOK #242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -2915,8 +2914,7 @@
"menus": {
"chat/modelPicker": [
{
"command": "github.copilot.chat.manageModels",
"when": "github.copilot.byokEnabled"
"command": "github.copilot.chat.manageModels"
}
],
"editor/title": [
Expand Down
26 changes: 24 additions & 2 deletions src/extension/byok/common/byokProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,30 @@ export function resolveModelInfo(modelId: string, providerName: string, knownMod
};
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for comments if they were not there before. I know some devs prefer code with 0 comments.

* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @returns true if BYOK should be enabled for this user
* @returns whether BYOK should be enabled for this user

*/
export function isBYOKEnabled(copilotToken: Omit<CopilotToken, "token">, 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;
}
22 changes: 21 additions & 1 deletion src/extension/byok/vscode-node/byokContribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -31,6 +31,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution {
private _registeredModelDisposables = new Map<string, VSCodeDisposable>();
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,
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How often is the registerModelCommand called?

If it is called only once the user adds a BYOK model - then I think this would work.

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.';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might need to improve this string to be Localized. I am not 100% sure how to do it from this extension, but ask agent mode.

const learnMore = 'Learn More';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for all these other strings. I think all user facing strings need a special syntax to be localized.

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) {
Expand Down
8 changes: 4 additions & 4 deletions src/extension/byok/vscode-node/byokUIService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ function createQuickPickWithBackButton<T extends QuickPickItem>(


async function createErrorModal(errorMessage: string, currentStep: ConfigurationStep): Promise<StateResult> {
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') {
Expand Down Expand Up @@ -322,7 +322,7 @@ export class BYOKUIService {

// Use manual quick pick creation for item button handling
const quickPick = window.createQuickPick<ProviderQuickPickItem>();
quickPick.title = 'Manage Models - Preview';
quickPick.title = 'Manage Models';
quickPick.ignoreFocusOut = false;
quickPick.placeholder = 'Select a provider';
quickPick.items = quickPickItems;
Expand Down Expand Up @@ -408,7 +408,7 @@ export class BYOKUIService {
const quickPick = window.createQuickPick<ModelQuickPickItem>();
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;
Expand Down Expand Up @@ -758,7 +758,7 @@ export class BYOKUIService {

private async promptForAPIKey(contextName: string, reconfigure: boolean = false): Promise<string | undefined> {
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,
Expand Down