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" "name": "Apple"
} }
}, },
"providerExtensionId": "vscode.github-authentication",
"providerUriSetting": "github-enterprise.uri", "providerUriSetting": "github-enterprise.uri",
"providerScopes": [ "providerScopes": [
[ [

View File

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

View File

@@ -4,11 +4,17 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ICommandService } from '../../../../../platform/commands/common/commands.js'; 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 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 = { const defaultChat = {
completionsRefreshTokenCommand: product.defaultChatAgent?.completionsRefreshTokenCommand ?? '', completionsRefreshTokenCommand: product.defaultChatAgent?.completionsRefreshTokenCommand ?? '',
chatRefreshTokenCommand: product.defaultChatAgent?.chatRefreshTokenCommand ?? '', chatRefreshTokenCommand: product.defaultChatAgent?.chatRefreshTokenCommand ?? '',
providerExtensionId: product.defaultChatAgent?.providerExtensionId ?? '',
}; };
export type InstallChatClassification = { export type InstallChatClassification = {
@@ -59,3 +65,44 @@ export function refreshTokens(commandService: ICommandService): void {
commandService.executeCommand(defaultChat.completionsRefreshTokenCommand); commandService.executeCommand(defaultChat.completionsRefreshTokenCommand);
commandService.executeCommand(defaultChat.chatRefreshTokenCommand); 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 { ChatEntitlement, ChatEntitlementContext, ChatEntitlementRequests, isProUser } from '../../../../services/chat/common/chatEntitlementService.js';
import { CHAT_OPEN_ACTION_ID } from '../actions/chatActions.js'; import { CHAT_OPEN_ACTION_ID } from '../actions/chatActions.js';
import { ChatViewId, ChatViewContainerId } from '../chat.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 { IDefaultAccount } from '../../../../../base/common/defaultAccount.js';
import { IDefaultAccountService } from '../../../../../platform/defaultAccount/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 }> { 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 entitlements;
let defaultAccount; let defaultAccount;
try { 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 { IPosition } from '../../../../../editor/common/core/position.js';
import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js'; import { IMarker, IMarkerService, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';
import { ChatSetupController } from './chatSetupController.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 { ChatSetup } from './chatSetupRunner.js';
import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js'; import { chatViewsWelcomeRegistry } from '../viewsWelcome/chatViewsWelcome.js';
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js'; import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js'; import { IDefaultAccountService } from '../../../../../platform/defaultAccount/common/defaultAccount.js';
import { IHostService } from '../../../../services/host/browser/host.js'; import { IHostService } from '../../../../services/host/browser/host.js';
import { IOutputService } from '../../../../services/output/common/output.js'; import { IOutputService } from '../../../../services/output/common/output.js';
import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
const defaultChat = { const defaultChat = {
extensionId: product.defaultChatAgent?.extensionId ?? '', extensionId: product.defaultChatAgent?.extensionId ?? '',
@@ -197,6 +198,8 @@ export class SetupAgent extends Disposable implements IChatAgentImplementation {
@IViewsService private readonly viewsService: IViewsService, @IViewsService private readonly viewsService: IViewsService,
@IContextKeyService private readonly contextKeyService: IContextKeyService, @IContextKeyService private readonly contextKeyService: IContextKeyService,
@IOutputService private readonly outputService: IOutputService, @IOutputService private readonly outputService: IOutputService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@ICommandService private readonly commandService: ICommandService,
) { ) {
super(); 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> { 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 widget = chatWidgetService.getWidgetBySessionResource(requestModel.session.sessionResource);
const modeInfo = widget?.input.currentModeInfo; const modeInfo = widget?.input.currentModeInfo;