Chat sign-in: auto-enable GitHub Authentication extension if silently disabled (#296303)

* Initial plan

* Auto-enable GitHub Authentication extension during chat sign-in if disabled

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>

* Apply during the chat request

* feedback

* delete the tests that didn't exist in the first place

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
Co-authored-by: Tyler Leonhardt <tyleonha@microsoft.com>
This commit is contained in:
Copilot
2026-02-20 08:32:18 +00:00
committed by GitHub
parent b78e12184a
commit 39e42e1e67
5 changed files with 68 additions and 2 deletions

View File

@@ -116,6 +116,7 @@
"name": "Apple"
}
},
"providerExtensionId": "vscode.github-authentication",
"providerUriSetting": "github-enterprise.uri",
"providerScopes": [
[

View File

@@ -360,6 +360,7 @@ export interface IDefaultChatAgent {
apple: { id: string; name: string };
};
readonly providerExtensionId: string;
readonly providerUriSetting: string;
readonly providerScopes: string[][];

View File

@@ -4,11 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
import { ILogService } from '../../../../../platform/log/common/log.js';
import product from '../../../../../platform/product/common/product.js';
import { localize } from '../../../../../nls.js';
import { EnablementState } from '../../../../services/extensionManagement/common/extensionManagement.js';
import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
const defaultChat = {
completionsRefreshTokenCommand: product.defaultChatAgent?.completionsRefreshTokenCommand ?? '',
chatRefreshTokenCommand: product.defaultChatAgent?.chatRefreshTokenCommand ?? '',
providerExtensionId: product.defaultChatAgent?.providerExtensionId ?? '',
};
export type InstallChatClassification = {
@@ -59,3 +65,44 @@ export function refreshTokens(commandService: ICommandService): void {
commandService.executeCommand(defaultChat.completionsRefreshTokenCommand);
commandService.executeCommand(defaultChat.chatRefreshTokenCommand);
}
/**
* Ensures the authentication provider extension is enabled.
* If the extension is found locally but disabled, it will be
* re-enabled and running extensions will be updated.
*
* @returns `true` if the extension was re-enabled, `false` otherwise.
*/
export async function maybeEnableAuthExtension(
extensionsWorkbenchService: IExtensionsWorkbenchService,
logService: ILogService
): Promise<boolean> {
if (!defaultChat.providerExtensionId) {
return false;
}
const providerExtension = extensionsWorkbenchService.local.find(
e => ExtensionIdentifier.equals(e.identifier.id, defaultChat.providerExtensionId)
);
if (!providerExtension) {
return false;
}
if (
providerExtension.enablementState === EnablementState.DisabledGlobally ||
providerExtension.enablementState === EnablementState.DisabledWorkspace
) {
logService.info(`[chat setup] auth provider extension '${defaultChat.providerExtensionId}' is disabled, re-enabling it`);
try {
await extensionsWorkbenchService.setEnablement([providerExtension], EnablementState.EnabledGlobally);
await extensionsWorkbenchService.updateRunningExtensions(localize('enableAuthExtension', "Enabling GitHub Authentication"));
return true;
} catch (error) {
logService.error(`[chat setup] failed to re-enable auth provider extension '${defaultChat.providerExtensionId}'`, error);
return false;
}
}
return false;
}

View File

@@ -28,7 +28,7 @@ import { IExtensionsWorkbenchService } from '../../../extensions/common/extensio
import { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, isProUser } from '../../../../services/chat/common/chatEntitlementService.js';
import { CHAT_OPEN_ACTION_ID } from '../actions/chatActions.js';
import { ChatViewId, ChatViewContainerId } from '../chat.js';
import { ChatSetupAnonymous, ChatSetupStep, ChatSetupResultValue, InstallChatEvent, InstallChatClassification, refreshTokens } from './chatSetup.js';
import { ChatSetupAnonymous, ChatSetupStep, ChatSetupResultValue, InstallChatEvent, InstallChatClassification, refreshTokens, maybeEnableAuthExtension } from './chatSetup.js';
import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js';
import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js';
@@ -153,6 +153,11 @@ export class ChatSetupController extends Disposable {
}
private async signIn(options: IChatSetupControllerOptions): Promise<{ defaultAccount: IDefaultAccount | undefined; entitlement: ChatEntitlement | undefined }> {
const authExtensionReEnabled = await maybeEnableAuthExtension(this.extensionsWorkbenchService, this.logService);
if (authExtensionReEnabled) {
refreshTokens(this.commandService);
}
let entitlements;
let defaultAccount;
try {

View File

@@ -47,13 +47,14 @@ import { ACTION_START as INLINE_CHAT_START } from '../../../inlineChat/common/in
import { IPosition } from '../../../../../editor/common/core/position.js';
import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';
import { ChatSetupController } from './chatSetupController.js';
import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult } from './chatSetup.js';
import { ChatSetupAnonymous, ChatSetupStep, IChatSetupResult, maybeEnableAuthExtension, refreshTokens } from './chatSetup.js';
import { ChatSetup } from './chatSetupRunner.js';
import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js';
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js';
import { IHostService } from '../../../../services/host/browser/host.js';
import { IOutputService } from '../../../../services/output/common/output.js';
import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
const defaultChat = {
extensionId: product.defaultChatAgent?.extensionId ?? '',
@@ -197,6 +198,8 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation {
@IViewsService private readonly viewsService: IViewsService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IOutputService private readonly outputService: IOutputService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
@@ -315,6 +318,15 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation {
}
private async doForwardRequestToChatWhenReady(requestModel: IChatRequestModel, progress: (part: IChatProgress) => void, chatService: IChatService, languageModelsService: ILanguageModelsService, chatAgentService: IChatAgentService, chatWidgetService: IChatWidgetService, languageModelToolsService: ILanguageModelToolsService): Promise<void> {
// Ensure auth extension is enabled before waiting for chat readiness.
// This must run before the readiness event listeners are set up because
// updateRunningExtensions restarts all extension hosts.
const authExtensionReEnabled = await maybeEnableAuthExtension(this.extensionsWorkbenchService, this.logService);
if (authExtensionReEnabled) {
refreshTokens(this.commandService);
}
const widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource);
const modeInfo = widget?.input.currentModeInfo;