Skip to content
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
4 changes: 2 additions & 2 deletions extensions/copilot/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ Object.assign(product, {
'name': 'GitHub'
},
'enterprise': {
'id': 'github-enterprise',
'id': 'github-enterprise-copilot',
'name': 'GHE.com'
},
'google': {
Expand All @@ -401,7 +401,7 @@ Object.assign(product, {
'name': 'Apple'
}
},
'providerUriSetting': 'github-enterprise.uri',
'providerUriSetting': 'github.copilot.enterprise.uri',
'providerScopes': [
[
'user:email'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@

import * as l10n from '@vscode/l10n';
import type { CancellationToken, McpHttpServerDefinition, McpServerDefinitionProvider } from 'vscode';
import { authProviderId, IAuthenticationService } from '../../../platform/authentication/common/authentication';
import { authProviderId, COPILOT_GITHUB_ENTERPRISE_URI_SETTING, getCopilotEnterpriseUri, IAuthenticationService, LEGACY_GITHUB_ENTERPRISE_URI_SETTING } from '../../../platform/authentication/common/authentication';
import { AuthProviderId, ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
import { ILogService } from '../../../platform/log/common/logService';
import { Event } from '../../../util/vs/base/common/event';
import { URI } from '../../../util/vs/base/common/uri';

const EnterpriseURLConfig = 'github-enterprise.uri';

export class GitHubMcpDefinitionProvider implements McpServerDefinitionProvider<McpHttpServerDefinition> {

readonly onDidChangeMcpServerDefinitions: Event<void>;
Expand Down Expand Up @@ -52,8 +50,8 @@ export class GitHubMcpDefinitionProvider implements McpServerDefinitionProvider<
return true;
}
// If they change the GHE URL
if (e.affectsConfiguration(EnterpriseURLConfig)) {
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub Enterprise URL.');
if (e.affectsConfiguration(COPILOT_GITHUB_ENTERPRISE_URI_SETTING) || e.affectsConfiguration(LEGACY_GITHUB_ENTERPRISE_URI_SETTING)) {
logService.debug('GitHubMcpDefinitionProvider: Configuration change affects GitHub Enterprise URL configuration.');
return true;
Comment thread
slang25 marked this conversation as resolved.
}
return false;
Expand Down Expand Up @@ -92,7 +90,7 @@ export class GitHubMcpDefinitionProvider implements McpServerDefinitionProvider<
}

private get gheConfig(): string | undefined {
return this.configurationService.getNonExtensionConfig<string>(EnterpriseURLConfig);
return getCopilotEnterpriseUri(this.configurationService);
}

private getGheUri(): URI {
Expand All @@ -113,7 +111,7 @@ export class GitHubMcpDefinitionProvider implements McpServerDefinitionProvider<
const channel = this.channel;
const isSignedIn = !!this.authenticationService.permissiveGitHubSession;

const basics = providerId === AuthProviderId.GitHubEnterprise
const basics = providerId === AuthProviderId.GitHubEnterpriseCopilot
? { label: 'GitHub Enterprise', uri: this.getGheUri() }
: { label: 'GitHub', uri: URI.parse('https://api.githubcopilot.com/mcp/') };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ describe('GitHubMcpDefinitionProvider', () => {
async function createProvider(configOverrides?: {
authProvider?: AuthProviderId;
gheUri?: string;
legacyGheUri?: string;
toolsets?: string[];
readonly?: boolean;
lockdown?: boolean;
Expand All @@ -93,7 +94,10 @@ describe('GitHubMcpDefinitionProvider', () => {
await configService.setConfig(ConfigKey.Shared.AuthProvider, configOverrides.authProvider);
}
if (configOverrides?.gheUri) {
await configService.setNonExtensionConfig('github-enterprise.uri', configOverrides.gheUri);
await configService.setNonExtensionConfig('github.copilot.enterprise.uri', configOverrides.gheUri);
}
if (configOverrides?.legacyGheUri) {
await configService.setNonExtensionConfig('github-enterprise.uri', configOverrides.legacyGheUri);
}
if (configOverrides?.toolsets) {
await configService.setConfig(ConfigKey.GitHubMcpToolsets, configOverrides.toolsets);
Expand Down Expand Up @@ -190,6 +194,29 @@ describe('GitHubMcpDefinitionProvider', () => {
expect(() => gheProviderWithoutUri.provideMcpServerDefinitions()).toThrow('GitHub Enterprise URI is not configured.');
});

test('falls back to the legacy GitHub Enterprise URI when the Copilot URI is unset', async () => {
const gheProvider = await createProvider({
authProvider: AuthProviderId.GitHubEnterprise,
legacyGheUri: 'https://fallback.enterprise.com'
});

const definitions = gheProvider.provideMcpServerDefinitions();

expect(definitions[0].uri.toString()).toBe('https://copilot-api.fallback.enterprise.com/mcp/');
});

test('falls back to the legacy GitHub Enterprise URI when the Copilot URI is blank', async () => {
const gheProvider = await createProvider({
authProvider: AuthProviderId.GitHubEnterprise,
gheUri: ' ',
legacyGheUri: 'https://fallback.enterprise.com'
});

const definitions = gheProvider.provideMcpServerDefinitions();

expect(definitions[0].uri.toString()).toBe('https://copilot-api.fallback.enterprise.com/mcp/');
});

test('includes X-MCP-Readonly header when readonly is true', async () => {
const readonlyProvider = await createProvider({ readonly: true });

Expand Down Expand Up @@ -323,6 +350,17 @@ describe('GitHubMcpDefinitionProvider', () => {
});

test('fires when GHE URI configuration changes', async () => {
await configService.setConfig(ConfigKey.Shared.AuthProvider, AuthProviderId.GitHubEnterprise);
await configService.setNonExtensionConfig('github.copilot.enterprise.uri', 'https://old.enterprise.com');

const eventPromise = Event.toPromise(provider.onDidChangeMcpServerDefinitions);

await configService.setNonExtensionConfig('github.copilot.enterprise.uri', 'https://new.enterprise.com');

await eventPromise;
});

test('fires when the legacy GHE URI changes while the Copilot URI is unset', async () => {
await configService.setConfig(ConfigKey.Shared.AuthProvider, AuthProviderId.GitHubEnterprise);
await configService.setNonExtensionConfig('github-enterprise.uri', 'https://old.enterprise.com');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ suite('Session tests', function () {
assert.strictEqual(readUserScopeSessionStub.calledOnce, false);
});

test('should use the github-enterprise provider if configured', async () => {
test('should use the Copilot enterprise provider if configured', async () => {
const configurationService = new InMemoryConfigurationService(
new DefaultsOnlyConfigurationService(),
new Map<Config<any>, unknown>([
Expand All @@ -200,7 +200,7 @@ suite('Session tests', function () {
const gheSessionId = 'ghe-session-id-1';

getAccountsStub.resolves([{ id: 'account', label: 'ghe-session-label' }]);
const gheSessionStub = getSessionStub.withArgs('github-enterprise', GITHUB_SCOPE_READ_USER, sinon.match.any);
const gheSessionStub = getSessionStub.withArgs('github-enterprise-copilot', GITHUB_SCOPE_READ_USER, sinon.match.any);
gheSessionStub.resolves({
id: gheSessionId,
accessToken: 'new-token',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export const GITHUB_SCOPE_READ_USER = ['read:user'];
// The same scopes that GitHub Pull Request, GitHub Repositories, and others use
export const GITHUB_SCOPE_ALIGNED = ['read:user', 'user:email', 'repo', 'workflow'];

export const COPILOT_GITHUB_ENTERPRISE_URI_SETTING = 'github.copilot.enterprise.uri';
export const LEGACY_GITHUB_ENTERPRISE_URI_SETTING = 'github-enterprise.uri';

export class MinimalModeError extends Error {
constructor() {
super('The authentication service is in minimal mode.');
Expand Down Expand Up @@ -339,9 +342,28 @@ export abstract class BaseAuthenticationService extends Disposable implements IA
}

export function authProviderId(configurationService: IConfigurationService): AuthProviderId {
const configuredProvider = configurationService.getConfig(ConfigKey.Shared.AuthProvider);
return (
configurationService.getConfig(ConfigKey.Shared.AuthProvider) === AuthProviderId.GitHubEnterprise
? AuthProviderId.GitHubEnterprise
isGitHubEnterpriseAuthProvider(configuredProvider)
? AuthProviderId.GitHubEnterpriseCopilot
: AuthProviderId.GitHub
);
}

export function isGitHubEnterpriseAuthProvider(providerId: string | undefined): boolean {
return providerId === AuthProviderId.GitHubEnterprise || providerId === AuthProviderId.GitHubEnterpriseCopilot;
}

export function getCopilotEnterpriseUri(configurationService: IConfigurationService): string | undefined {
const copilotUri = configurationService.getNonExtensionConfig<string>(COPILOT_GITHUB_ENTERPRISE_URI_SETTING);
if (typeof copilotUri === 'string' && copilotUri.trim().length > 0) {
return copilotUri;
}

const legacyUri = configurationService.getNonExtensionConfig<string>(LEGACY_GITHUB_ENTERPRISE_URI_SETTING);
if (typeof legacyUri === 'string' && legacyUri.trim().length > 0) {
return legacyUri;
}

return undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export const enum CHAT_MODEL {
export enum AuthProviderId {
GitHub = 'github',
GitHubEnterprise = 'github-enterprise',
GitHubEnterpriseCopilot = 'github-enterprise-copilot',
Microsoft = 'microsoft',
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import { Emitter, Event } from '../../../util/vs/base/common/event';
import { Disposable } from '../../../util/vs/base/common/lifecycle';
import { CopilotToken } from '../../authentication/common/copilotToken';
import { ICopilotTokenStore } from '../../authentication/common/copilotTokenStore';
import { AuthProviderId, ConfigKey, CopilotConfigPrefix, IConfigurationService } from '../../configuration/common/configurationService';
import { COPILOT_GITHUB_ENTERPRISE_URI_SETTING, getCopilotEnterpriseUri, isGitHubEnterpriseAuthProvider, LEGACY_GITHUB_ENTERPRISE_URI_SETTING } from '../../authentication/common/authentication';
import { ConfigKey, CopilotConfigPrefix, IConfigurationService } from '../../configuration/common/configurationService';
import { ICAPIClientService } from '../common/capiClient';
import { IDomainChangeEvent, IDomainService } from '../common/domainService';

const EnterpriseURLConfig = 'github-enterprise.uri';

export class DomainService extends Disposable implements IDomainService {
declare readonly _serviceBrand: undefined;

Expand All @@ -36,7 +35,8 @@ export class DomainService extends Disposable implements IDomainService {
// Updated configs that have to do with GHE Domains
if (
event.affectsConfiguration(`${CopilotConfigPrefix}.advanced`) ||
event.affectsConfiguration(EnterpriseURLConfig)
event.affectsConfiguration(COPILOT_GITHUB_ENTERPRISE_URI_SETTING) ||
event.affectsConfiguration(LEGACY_GITHUB_ENTERPRISE_URI_SETTING)
) {
this._processCAPIModuleChange(this._tokenStore.copilotToken);
}
Expand All @@ -51,7 +51,9 @@ export class DomainService extends Disposable implements IDomainService {
if (proxyConfigUrl) {
proxyConfigUrl = proxyConfigUrl.replace(/\/$/, '');
}
const enterpriseValue = this._configurationService.getConfig(ConfigKey.Shared.AuthProvider) === AuthProviderId.GitHubEnterprise ? this._configurationService.getNonExtensionConfig<string>(EnterpriseURLConfig) : undefined;
const enterpriseValue = isGitHubEnterpriseAuthProvider(this._configurationService.getConfig(ConfigKey.Shared.AuthProvider))
? getCopilotEnterpriseUri(this._configurationService)
: undefined;
const moduleToken = {
endpoints: {
api: capiConfigUrl || token?.endpoints?.api,
Expand Down
16 changes: 15 additions & 1 deletion extensions/github-authentication/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"untrustedWorkspaces": {
"supported": "limited",
"restrictedConfigurations": [
"github-enterprise.uri"
"github-enterprise.uri",
"github.copilot.enterprise.uri"
]
}
},
Expand All @@ -47,6 +48,13 @@
"authorizationServerGlobs": [
"*"
]
},
{
"label": "GitHub Copilot Enterprise",
"id": "github-enterprise-copilot",
"authorizationServerGlobs": [
"*"
]
}
],
"configuration": [
Expand All @@ -58,6 +66,12 @@
"markdownDescription": "%config.github-enterprise.uri.description%",
"pattern": "^(?:$|(https?)://(?!github\\.com).*)"
},
"github.copilot.enterprise.uri": {
"type": "string",
"scope": "application",
"markdownDescription": "%config.github-copilot-enterprise.uri.description%",
"pattern": "^(?:$|(https?)://(?!github\\.com).*)"
},
"github-authentication.useElectronFetch": {
"type": "boolean",
"default": true,
Expand Down
1 change: 1 addition & 0 deletions extensions/github-authentication/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"description": "GitHub Authentication Provider",
"config.github-enterprise.title": "GHE.com & GitHub Enterprise Server Authentication",
"config.github-enterprise.uri.description": "The URI for your GHE.com or GitHub Enterprise Server instance.\n\nExamples:\n* GHE.com: `https://octocat.ghe.com`\n* GitHub Enterprise Server: `https://github.octocat.com`\n\n> **Note:** This should _not_ be set to a GitHub.com URI. If your account exists on GitHub.com or is a GitHub Enterprise Managed User, you do not need any additional configuration and can simply log in to GitHub.",
"config.github-copilot-enterprise.uri.description": "The URI for the GHE.com or GitHub Enterprise Server instance used by GitHub Copilot.\n\nExamples:\n* GHE.com: `https://octocat.ghe.com`\n* GitHub Enterprise Server: `https://github.octocat.com`\n\n> **Note:** This setting lets GitHub Copilot use a different enterprise host than other GitHub extensions. If unset, GitHub Copilot falls back to `github-enterprise.uri`.",
"config.github-authentication.useElectronFetch.description": "When true, uses Electron's built-in fetch function for HTTP requests. When false, uses the Node.js global fetch function. This setting only applies when running in the Electron environment. **Note:** A restart is required for this setting to take effect.",
"config.github-authentication.preferDeviceCodeFlow.description": "When true, prioritize the device code flow for authentication instead of other available flows. This is useful for environments like WSL where the local server or URL handler flows may not work as expected."
}
Loading