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