Skip to content
Draft
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: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2903,6 +2903,12 @@
"default": true,
"markdownDescription": "%github.copilot.config.backgroundAgent.enabled%"
},
"github.copilot.chat.bringYourOwnKey.enabled": {
"type": "boolean",
"default": true,
"scope": "machine",
"markdownDescription": "%github.copilot.config.bringYourOwnKey.enabled%"
},
"github.copilot.chat.cloudAgent.enabled": {
"type": "boolean",
"default": true,
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
"github.copilot.config.cli.isolationOption.enabled": "Enable the isolation mode option for Background Agents. When enabled, users can choose between Worktree and Workspace modes.",
"github.copilot.config.cli.sessionController.enabled": "Enable the new session controller API for Background Agents. Requires VS Code reload.",
"github.copilot.config.backgroundAgent.enabled": "Enable the Background Agent. When disabled, the Background Agent will not be available in 'Continue In' context menus.",
"github.copilot.config.bringYourOwnKey.enabled": "Enable Bring Your Own Key (BYOK) model providers. When disabled, users cannot configure custom API keys for third-party model providers. This setting can be managed by an organization via device management policies.",
"github.copilot.config.cloudAgent.enabled": "Enable the Cloud Agent. When disabled, the Cloud Agent will not be available in 'Continue In' context menus.",
"github.copilot.config.copilotMemory.enabled": "Enable agentic memory for GitHub Copilot. When enabled, Copilot can store repository-scoped facts about your codebase conventions, structure, and preferences remotely on GitHub, and recall them in future conversations to provide more contextually relevant assistance. [Learn more](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/copilot-memory).",
"github.copilot.config.tools.memory.enabled": "Enable the memory tool to let the agent save and recall notes during a conversation. Memories are stored locally in VS Code storage — user-scoped memories persist across workspaces and sessions, while session-scoped memories are cleared when the conversation ends.",
Expand Down
10 changes: 7 additions & 3 deletions src/extension/byok/common/byokProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import * as l10n from '@vscode/l10n';
import type { Disposable, LanguageModelChatInformation, LanguageModelDataPart, LanguageModelTextPart, LanguageModelThinkingPart, LanguageModelToolCallPart, LanguageModelToolResultPart } from 'vscode';
import { CopilotToken } from '../../../platform/authentication/common/copilotToken';
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { ICAPIClientService } from '../../../platform/endpoint/common/capiClient';
import { EndpointEditToolName, IChatModelInformation, ModelSupportedEndpoint } from '../../../platform/endpoint/common/endpointProvider';
import { isScenarioAutomation } from '../../../platform/env/common/envService';
Expand Down Expand Up @@ -174,14 +175,17 @@ export function byokKnownModelToAPIInfo(providerName: string, id: string, capabi
};
}

export function isBYOKEnabled(copilotToken: Omit<CopilotToken, 'token'>, capiClientService: ICAPIClientService): boolean {
export function isBYOKEnabled(copilotToken: Omit<CopilotToken, 'token'>, capiClientService: ICAPIClientService, configurationService: IConfigurationService): boolean {
if (isScenarioAutomation) {
return true;
}

if (!configurationService.getConfig(ConfigKey.BYOKEnabled)) {
return false;
}

const isGHE = capiClientService.dotcomAPIURL !== 'https://api.github.com';
const byokAllowed = (copilotToken.isInternal || copilotToken.isIndividual) && !isGHE;
return byokAllowed;
return !isGHE;
}

/**
Expand Down
74 changes: 74 additions & 0 deletions src/extension/byok/common/test/byokProvider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { describe, expect, it } from 'vitest';
import { CopilotToken } from '../../../../platform/authentication/common/copilotToken';
import { ConfigKey } from '../../../../platform/configuration/common/configurationService';
import { DefaultsOnlyConfigurationService } from '../../../../platform/configuration/common/defaultsOnlyConfigurationService';
import { ICAPIClientService } from '../../../../platform/endpoint/common/capiClient';
import { InMemoryConfigurationService } from '../../../../platform/configuration/test/common/inMemoryConfigurationService';
import { isBYOKEnabled } from '../byokProvider';

function createMockCopilotToken(overrides: { individual?: boolean; organization_list?: string[] } = {}): Omit<CopilotToken, 'token'> {
return {
sku: undefined,
isIndividual: overrides.individual ?? false,
organizationList: overrides.organization_list ?? [],
organizationLoginList: [],
enterpriseList: [],
endpoints: undefined,
isInternal: false,
isMicrosoftInternal: false,
isGitHubInternal: false,
isVscodeTeamMember: false,
username: undefined,
telemetryId: undefined,
copilotUserQuotaInfo: undefined,
modelIds: undefined,
} as unknown as Omit<CopilotToken, 'token'>;
}

function createMockCAPIClientService(dotcomAPIURL = 'https://api.github.com'): ICAPIClientService {
return { dotcomAPIURL } as ICAPIClientService;
}

describe('isBYOKEnabled', () => {
it('should return true for dotcom user when setting is enabled (default)', () => {
const configService = new InMemoryConfigurationService(new DefaultsOnlyConfigurationService());
const token = createMockCopilotToken();
const capiClient = createMockCAPIClientService();
expect(isBYOKEnabled(token, capiClient, configService)).toBe(true);
});

it('should return true for business/enterprise users on dotcom', () => {
const configService = new InMemoryConfigurationService(new DefaultsOnlyConfigurationService());
const token = createMockCopilotToken({ individual: false, organization_list: ['some-org'] });
const capiClient = createMockCAPIClientService();
expect(isBYOKEnabled(token, capiClient, configService)).toBe(true);
});

it('should return false for GHE users even when setting is enabled', () => {
const configService = new InMemoryConfigurationService(new DefaultsOnlyConfigurationService());
const token = createMockCopilotToken({ individual: true });
const capiClient = createMockCAPIClientService('https://ghe.example.com/api/v3');
expect(isBYOKEnabled(token, capiClient, configService)).toBe(false);
});

it('should return false when the BYOKEnabled setting is disabled', () => {
const configService = new InMemoryConfigurationService(new DefaultsOnlyConfigurationService());
configService.setConfig(ConfigKey.BYOKEnabled, false);
const token = createMockCopilotToken({ individual: true });
const capiClient = createMockCAPIClientService();
expect(isBYOKEnabled(token, capiClient, configService)).toBe(false);
});

it('should return false when BYOKEnabled setting is disabled even for business users', () => {
const configService = new InMemoryConfigurationService(new DefaultsOnlyConfigurationService());
configService.setConfig(ConfigKey.BYOKEnabled, false);
const token = createMockCopilotToken({ individual: false, organization_list: ['some-org'] });
const capiClient = createMockCAPIClientService();
expect(isBYOKEnabled(token, capiClient, configService)).toBe(false);
});
});
4 changes: 3 additions & 1 deletion src/extension/byok/vscode-node/byokContribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { LanguageModelChatInformation, LanguageModelChatProvider, lm } from 'vscode';
import { IAuthenticationService } from '../../../platform/authentication/common/authentication';
import { IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { ICAPIClientService } from '../../../platform/endpoint/common/capiClient';
import { IVSCodeExtensionContext } from '../../../platform/extContext/common/extensionContext';
import { ILogService } from '../../../platform/log/common/logService';
Expand Down Expand Up @@ -35,6 +36,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution {
@IVSCodeExtensionContext extensionContext: IVSCodeExtensionContext,
@IAuthenticationService authService: IAuthenticationService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
) {
super();
this._byokStorageService = new BYOKStorageService(extensionContext);
Expand All @@ -46,7 +48,7 @@ export class BYOKContrib extends Disposable implements IExtensionContribution {
}

private async _authChange(authService: IAuthenticationService, instantiationService: IInstantiationService) {
if (authService.copilotToken && isBYOKEnabled(authService.copilotToken, this._capiClientService) && !this._byokProvidersRegistered) {
if (authService.copilotToken && isBYOKEnabled(authService.copilotToken, this._capiClientService, this._configurationService) && !this._byokProvidersRegistered) {
this._byokProvidersRegistered = true;
// Update known models list from CDN so all providers have the same list
const knownModels = await this.fetchKnownModelList(this._fetcherService);
Expand Down
3 changes: 2 additions & 1 deletion src/platform/configuration/common/configurationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,8 @@ export namespace ConfigKey {
export const BackgroundCompaction = defineSetting<boolean>('chat.backgroundCompaction', ConfigType.ExperimentBased, false);
export const VirtualToolThreshold = defineSetting<number>('chat.virtualTools.threshold', ConfigType.ExperimentBased, HARD_TOOL_LIMIT);
export const CurrentEditorAgentContext = defineSetting<boolean>('chat.agent.currentEditorContext.enabled', ConfigType.Simple, true);
/** BYOK */
/** Enable or disable Bring Your Own Key (BYOK) model providers */
export const BYOKEnabled = defineSetting<boolean>('chat.bringYourOwnKey.enabled', ConfigType.Simple, true);
export const AutoFixDiagnostics = defineSetting<boolean>('chat.agent.autoFix', ConfigType.ExperimentBased, false);
export const NotebookFollowCellExecution = defineSetting<boolean>('chat.notebook.followCellExecution.enabled', ConfigType.Simple, false);
export const UseAlternativeNESNotebookFormat = defineSetting<boolean>('chat.notebook.enhancedNextEditSuggestions.enabled', ConfigType.ExperimentBased, false);
Expand Down