Path: blob/main/extensions/copilot/src/extension/contextKeys/vscode-node/contextKeys.contribution.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import { commands, extensions, window } from 'vscode';5import { IAuthenticationService, MinimalModeError } from '../../../platform/authentication/common/authentication';6import { ContactSupportError, EnterpriseManagedError, GitHubLoginFailedError, InvalidTokenError, NotSignedUpError, RateLimitedError, SubscriptionExpiredError } from '../../../platform/authentication/vscode-node/copilotTokenManager';7import { SESSION_LOGIN_MESSAGE } from '../../../platform/authentication/vscode-node/session';8import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';9import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';10import { IEnvService } from '../../../platform/env/common/envService';11import { ILogService } from '../../../platform/log/common/logService';12import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';13import { TelemetryData } from '../../../platform/telemetry/common/telemetryData';14import { Disposable } from '../../../util/vs/base/common/lifecycle';15import { autorun } from '../../../util/vs/base/common/observableInternal';16import { GHPR_EXTENSION_ID } from '../../chatSessions/vscode/chatSessionsUriHandler';17import { EXTENSION_ID } from '../../common/constants';1819const welcomeViewContextKeys = {20Activated: 'github.copilot-chat.activated',21Offline: 'github.copilot.offline',22IndividualDisabled: 'github.copilot.interactiveSession.individual.disabled',23IndividualExpired: 'github.copilot.interactiveSession.individual.expired',24ContactSupport: 'github.copilot.interactiveSession.contactSupport',25EnterpriseDisabled: 'github.copilot.interactiveSession.enterprise.disabled',26InvalidToken: 'github.copilot.interactiveSession.invalidToken',27RateLimited: 'github.copilot.interactiveSession.rateLimited',28GitHubLoginFailed: 'github.copilot.interactiveSession.gitHubLoginFailed',29};3031const chatQuotaExceededContextKey = 'github.copilot.chat.quotaExceeded';3233const showLogViewContextKey = `github.copilot.chat.showLogView`;34const debugReportFeedbackContextKey = 'github.copilot.debugReportFeedback';3536const previewFeaturesDisabledContextKey = 'github.copilot.previewFeaturesDisabled';3738const clientByokEnabledContextKey = 'github.copilot.clientByokEnabled';3940const debugContextKey = 'github.copilot.chat.debug';4142const missingPermissiveSessionContextKey = 'github.copilot.auth.missingPermissiveSession';4344export const prExtensionInstalledContextKey = 'github.copilot.prExtensionInstalled';4546const sessionSearchEnabledContextKey = 'github.copilot.sessionSearch.enabled';4748export class ContextKeysContribution extends Disposable {4950private _needsOfflineCheck = false;51private _scheduledOfflineCheck: TimeoutHandle | undefined;52private _showLogView = false;53private _lastContextKey: string | undefined;5455constructor(56@IAuthenticationService private readonly _authenticationService: IAuthenticationService,57@ITelemetryService private readonly _telemetryService: ITelemetryService,58@ILogService private readonly _logService: ILogService,59@IConfigurationService private readonly _configService: IConfigurationService,60@IEnvService private readonly _envService: IEnvService,61@IExperimentationService private readonly _expService: IExperimentationService62) {63super();6465void this._inspectContext().catch(console.error);66void this._updatePermissiveSessionContext().catch(console.error);67void this._updateClientByokEnabledContext().catch(console.error);68this._register(_authenticationService.onDidAuthenticationChange(async () => await this._onAuthenticationChange()));69this._register(commands.registerCommand('github.copilot.refreshToken', async () => await this._inspectContext()));70this._register(commands.registerCommand('github.copilot.debug.showChatLogView', async () => {71this._showLogView = true;72await commands.executeCommand('setContext', showLogViewContextKey, true);73await commands.executeCommand('copilot-chat.focus');74}));75this._register({ dispose: () => this._cancelPendingOfflineCheck() });76this._register(window.onDidChangeWindowState(() => this._runOfflineCheck('Window state change')));7778this._updateShowLogViewContext();79this._updateDebugContext();80this._updatePrExtensionInstalledContext();8182const debugReportFeedback = this._configService.getConfigObservable(ConfigKey.TeamInternal.DebugReportFeedback);83this._register(autorun(reader => {84commands.executeCommand('setContext', debugReportFeedbackContextKey, debugReportFeedback.read(reader));85}));8687const sessionSearchEnabled = this._configService.getExperimentBasedConfigObservable(ConfigKey.LocalIndexEnabled, this._expService);88this._register(autorun(reader => {89commands.executeCommand('setContext', sessionSearchEnabledContextKey, sessionSearchEnabled.read(reader));90}));9192// Listen for extension changes to update PR extension installed context93this._register(extensions.onDidChange(() => {94this._updatePrExtensionInstalledContext();95}));96}9798private _scheduleOfflineCheck() {99this._cancelPendingOfflineCheck();100this._needsOfflineCheck = true;101this._logService.debug(`[context keys] Scheduling offline check. Active: ${window.state.active}, focused: ${window.state.focused}.`);102if (window.state.active && window.state.focused) {103const delayInSeconds = 60;104this._scheduledOfflineCheck = setTimeout(() => {105this._scheduledOfflineCheck = undefined;106this._runOfflineCheck('Scheduled offline check');107}, delayInSeconds * 1000);108}109}110111private _runOfflineCheck(trigger: string) {112this._logService.debug(`[context keys] ${trigger}. Needs offline check: ${this._needsOfflineCheck}, active: ${window.state.active}, focused: ${window.state.focused}.`);113if (this._needsOfflineCheck && window.state.active && window.state.focused) {114this._inspectContext()115.catch(err => this._logService.error(err));116}117}118119private _cancelPendingOfflineCheck() {120this._needsOfflineCheck = false;121if (this._scheduledOfflineCheck) {122clearTimeout(this._scheduledOfflineCheck);123this._scheduledOfflineCheck = undefined;124}125}126127private async _inspectContext() {128this._logService.debug(`[context keys] Updating context keys.`);129this._cancelPendingOfflineCheck();130const allKeys = Object.values(welcomeViewContextKeys);131let error: unknown | undefined = undefined;132let key: string | undefined;133try {134await this._authenticationService.getCopilotToken();135key = welcomeViewContextKeys.Activated;136} catch (e: any) {137error = e;138const reason = e.message || e;139const data = TelemetryData.createAndMarkAsIssued({ reason });140this._telemetryService.sendGHTelemetryErrorEvent('activationFailed', data.properties, data.measurements);141const message =142reason === 'GitHubLoginFailed'143? SESSION_LOGIN_MESSAGE144: `GitHub Copilot could not connect to server. Extension activation failed: "${reason}"`;145this._logService.error(message);146}147148if (error instanceof NotSignedUpError) {149key = welcomeViewContextKeys.IndividualDisabled;150} else if (error instanceof SubscriptionExpiredError) {151key = welcomeViewContextKeys.IndividualExpired;152} else if (error instanceof EnterpriseManagedError) {153key = welcomeViewContextKeys.EnterpriseDisabled;154} else if (error instanceof ContactSupportError) {155key = welcomeViewContextKeys.ContactSupport;156} else if (error instanceof InvalidTokenError) {157key = welcomeViewContextKeys.InvalidToken;158} else if (error instanceof GitHubLoginFailedError) {159key = welcomeViewContextKeys.GitHubLoginFailed;160} else if (error) {161if (!extensions.getExtension(EXTENSION_ID)?.isActive) {162if (error instanceof RateLimitedError) {163key = welcomeViewContextKeys.RateLimited;164} else {165key = welcomeViewContextKeys.Offline;166}167}168this._scheduleOfflineCheck();169}170171if (key) {172if (key !== this._lastContextKey) {173this._logService.info(`[context keys] Setting context key: ${key}`);174this._lastContextKey = key;175}176commands.executeCommand('setContext', key, true);177}178179// Unset all other context keys180for (const contextKey of allKeys) {181if (contextKey !== key) {182commands.executeCommand('setContext', contextKey, false);183}184}185186await this._updatePermissiveSessionContext();187}188189private async _updateQuotaExceededContext() {190try {191const copilotToken = await this._authenticationService.getCopilotToken();192commands.executeCommand('setContext', chatQuotaExceededContextKey, copilotToken.isChatQuotaExceeded);193} catch (e) {194commands.executeCommand('setContext', chatQuotaExceededContextKey, false);195}196}197198private async _updatePreviewFeaturesDisabledContext() {199try {200const copilotToken = await this._authenticationService.getCopilotToken();201const disabled = !copilotToken.isEditorPreviewFeaturesEnabled();202if (disabled) {203this._logService.warn(`Copilot preview features are disabled by organizational policy. Learn more: https://aka.ms/github-copilot-org-enable-features`);204}205commands.executeCommand('setContext', previewFeaturesDisabledContextKey, disabled);206} catch (e) {207commands.executeCommand('setContext', previewFeaturesDisabledContextKey, undefined);208}209}210211private async _updateClientByokEnabledContext() {212try {213const copilotToken = await this._authenticationService.getCopilotToken();214commands.executeCommand('setContext', clientByokEnabledContextKey, copilotToken.isClientBYOKEnabled());215} catch (e) {216commands.executeCommand('setContext', clientByokEnabledContextKey, undefined);217}218}219220private _updateShowLogViewContext() {221if (this._showLogView) {222return;223}224225this._showLogView = !!this._authenticationService.copilotToken?.isInternal || !this._envService.isProduction();226if (this._showLogView) {227commands.executeCommand('setContext', showLogViewContextKey, this._showLogView);228}229}230231private _updateDebugContext() {232commands.executeCommand('setContext', debugContextKey, !this._envService.isProduction());233}234235private _updatePrExtensionInstalledContext() {236const isPrExtensionInstalled = !!extensions.getExtension(GHPR_EXTENSION_ID);237commands.executeCommand('setContext', prExtensionInstalledContextKey, isPrExtensionInstalled);238}239240private async _onAuthenticationChange() {241this._inspectContext();242this._updateQuotaExceededContext();243this._updatePreviewFeaturesDisabledContext();244this._updateClientByokEnabledContext();245this._updateShowLogViewContext();246this._updatePermissiveSessionContext();247}248249private async _updatePermissiveSessionContext() {250let hasPermissiveSession = false;251let missingPermissiveSession = false;252if (!this._authenticationService.isMinimalMode) {253try {254hasPermissiveSession = !!(await this._authenticationService.getGitHubSession('permissive', { silent: true }));255} catch (error) {256if (!(error instanceof MinimalModeError)) {257this._logService.trace(`[context keys] Failed to resolve permissive session: ${error instanceof Error ? error.message : String(error)}`);258hasPermissiveSession = !!this._authenticationService.permissiveGitHubSession;259}260}261missingPermissiveSession = !hasPermissiveSession;262}263commands.executeCommand('setContext', missingPermissiveSessionContextKey, missingPermissiveSession);264}265}266267268