chatEntitlementService.ts ×52

Frontier kind: Code frontier

unlabeled · c_33629b78133f

1303 tests · 18286 LOC · 106 files · introduces 0 tests · 1538 LOC · 4 files

Introduces — evidence that enters the hierarchy at this concept

Code
60 ranges1538 lines · 4 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
2253 ranges18286 lines · 106 files · Browse complete extent
All tests (intent)
1303 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

4 files ranked by introduced lines: 1538 introduced LOC across 60 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/services/chat/common/chatEntitlementService.ts 651 introduced LOC · 52 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- chatEntitlementService.ts
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 >
6 > import product from '../../../../platform/product/common/product.js';
7 > import { Barrier } from '../../../../base/common/async.js';
8 > import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js';
9 > import { Emitter, Event } from '../../../../base/common/event.js';
10 > import { Lazy } from '../../../../base/common/lazy.js';
11 > import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
12 > import { IRequestContext } from '../../../../base/parts/request/common/request.js';
13 > import { localize } from '../../../../nls.js';
14 > import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
15 > import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
16 > import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
17 > import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
18 > import { ILogService, LogLevel } from '../../../../platform/log/common/log.js';
19 > import { IProductService } from '../../../../platform/product/common/productService.js';
20 > import { asText, IRequestService } from '../../../../platform/request/common/request.js';
21 > import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
22 > import { ITelemetryService, TelemetryLevel } from '../../../../platform/telemetry/common/telemetry.js';
23 > import { AuthenticationSession, IAuthenticationService } from '../../authentication/common/authentication.js';
24 > import { IOpenerService } from '../../../../platform/opener/common/opener.js';
25 > import { URI } from '../../../../base/common/uri.js';
26 > import Severity from '../../../../base/common/severity.js';
27 > import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
28 > import { isWeb } from '../../../../base/common/platform.js';
29 > import { ILifecycleService } from '../../lifecycle/common/lifecycle.js';
30 > import { Mutable } from '../../../../base/common/types.js';
31 > import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
32 > import { IObservable, observableFromEvent } from '../../../../base/common/observable.js';
33 > import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js';
34 > import { IDefaultAccount, IEntitlementsData } from '../../../../base/common/defaultAccount.js';
35 >
36 > export namespace ChatEntitlementContextKeys {
37 >
38 > export const Setup = {
39 > hidden: new RawContextKey<boolean>('chatSetupHidden', false, true), // True when chat setup is explicitly hidden.
40 > installed: new RawContextKey<boolean>('chatSetupInstalled', false, true), // True when the chat extension is installed and enabled.
41 > disabled: new RawContextKey<boolean>('chatSetupDisabled', false, true), // True when the chat extension is disabled due to any other reason than workspace trust.
42 > disabledInWorkspace: new RawContextKey<boolean>('chatSetupDisabledInWorkspace', false, true), // True when chat is disabled at the workspace level via settings.
43 > untrusted: new RawContextKey<boolean>('chatSetupUntrusted', false, true), // True when the chat extension is disabled due to workspace trust.
44 > later: new RawContextKey<boolean>('chatSetupLater', false, true), // True when the user wants to finish setup later.
45 > registered: new RawContextKey<boolean>('chatSetupRegistered', false, true), // True when the user has registered as Free or Pro user.
46 > completed: new RawContextKey<boolean>('chatSetupCompleted', false, true) // True when the user has completed the setup flow, regardless of the outcome.
47 > };
48 >
49 > export const Entitlement = {
50 > signedOut: new RawContextKey<boolean>('chatEntitlementSignedOut', false, true), // True when user is signed out.
51 > canSignUp: new RawContextKey<boolean>('chatPlanCanSignUp', false, true), // True when user can sign up to be a chat free user.
52 >
53 > planFree: new RawContextKey<boolean>('chatPlanFree', false, true), // True when user is a chat free user.
54 > planPro: new RawContextKey<boolean>('chatPlanPro', false, true), // True when user is a chat pro user.
55 > planEdu: new RawContextKey<boolean>('chatPlanEdu', false, true), // True when user is a chat edu user.
56 > planProPlus: new RawContextKey<boolean>('chatPlanProPlus', false, true), // True when user is a chat pro plus user.
57 > planMax: new RawContextKey<boolean>('chatPlanMax', false, true), // True when user is a chat max user.
58 > planBusiness: new RawContextKey<boolean>('chatPlanBusiness', false, true), // True when user is a chat business user.
59 > planEnterprise: new RawContextKey<boolean>('chatPlanEnterprise', false, true), // True when user is a chat enterprise user.
60 >
61 > organisations: new RawContextKey<string[]>('chatEntitlementOrganisations', undefined, true), // The organizations the user belongs to.
62 > internal: new RawContextKey<boolean>('chatEntitlementInternal', false, true), // True when user belongs to internal organisation.
63 > sku: new RawContextKey<string>('chatEntitlementSku', undefined, true), // The SKU of the user.
64 > };
65 >
66 > export const chatQuotaExceeded = new RawContextKey<boolean>('chatQuotaExceeded', false, true);
67 > export const completionsQuotaExceeded = new RawContextKey<boolean>('completionsQuotaExceeded', false, true);
68 >
69 > export const chatAnonymous = new RawContextKey<boolean>('chatAnonymous', false, true);
70 >
71 > export const clientByokEnabled = new RawContextKey<boolean>('github.copilot.clientByokEnabled', true, true);
72 >
73 > export const hasByokModels = new RawContextKey<boolean>('github.copilot.hasByokModels', false, true);
74 > }
75 >
76 > export const IChatEntitlementService = createDecorator<IChatEntitlementService>('chatEntitlementService');
77 >
78 > export enum ChatEntitlement {
79 > /** Signed out */
80 > Unknown = 1,
81 > /** Signed in but not yet resolved */
82 > Unresolved = 2,
83 > /** Signed in and entitled to Free */
84 > Available = 3,
85 > /** Signed in but not entitled to Free */
86 > Unavailable = 4,
87 > /** Signed-up to Free */
88 > Free = 5,
89 > /** Signed-up to EDU */
90 > EDU = 10,
91 > /** Signed-up to Pro */
92 > Pro = 6,
93 > /** Signed-up to Pro Plus */
94 > ProPlus = 7,
95 > /** Signed-up to Business */
96 > Business = 8,
97 > /** Signed-up to Enterprise */
98 > Enterprise = 9,
99 > /** Signed-up to Max */
100 > Max = 11,
101 > }
102 >
103 > export interface IChatSentiment {
104 >
105 > /**
106 > * Whether the user has completed the setup flow or not, regardless of the outcome
107 > */
108 > completed?: boolean;
109 >
110 > /**
111 > * User has Chat installed.
112 > */
113 > installed?: boolean;
114 >
115 > /**
116 > * User signals no intent in using Chat.
117 > *
118 > * Note: in contrast to `disabled`, this should not only disable
119 > * Chat but also hide all of its UI.
120 > */
121 > hidden?: boolean;
122 >
123 > /**
124 > * User signals intent to disable Chat.
125 > *
126 > * Note: in contrast to `hidden`, this should not hide
127 > * Chat but but disable its functionality.
128 > */
129 > disabled?: boolean;
130 >
131 > /**
132 > * Chat is disabled at the workspace level
133 > *
134 > * Note: in contrast to `hidden` (which hides all UI globally),
135 > * this only disables Chat in the current workspace while
136 > * keeping its UI visible so the user can re-enable it.
137 > */
138 > disabledInWorkspace?: boolean;
139 >
140 > /**
141 > * Chat is disabled due to missing workspace trust.
142 > *
143 > * Note: even though this disables Chat, we want to treat it
144 > * different from the `disabled` state that is by explicit
145 > * user choice.
146 > */
147 > untrusted?: boolean;
148 >
149 > /**
150 > * User signals intent to use Chat later.
151 > */
152 > later?: boolean;
153 >
154 > /**
155 > * User has registered as Free or Pro user.
156 > */
157 > registered?: boolean;
158 > }
159 >
160 > /**
161 > * The inputs needed to decide whether Chat still requires the user to run setup
162 > * (sign in / sign up / trust / enable) before it can service a request.
163 > */
164 > export interface IChatSetupRequirement {
165 > /** Whether the setup flow has been completed (any outcome). */
166 > readonly completed: boolean;
167 > /** Whether the chat extension is disabled for a reason other than trust. */
168 > readonly disabled: boolean;
169 > /** Whether the chat extension is disabled because the workspace is untrusted. */
170 > readonly untrusted: boolean;
171 > /** The user's last known or resolved entitlement. */
172 > readonly entitlement: ChatEntitlement;
173 > /** Whether anonymous (signed-out) Chat access is enabled. */
174 > readonly anonymous: boolean;
175 > /** Whether BYOK models are available. */
176 > readonly hasByokModels: boolean;
177 > }
178 >
179 > /**
180 > * Single source of truth for whether Chat still requires setup before it can
181 > * service a request. Shared by the setup agent (which routes a sent message
182 > * through setup) and the model picker (which surfaces a "Sign in to use Copilot"
183 > * state instead of a misleading lone "Auto"). BYOK models and anonymous access
184 > * intentionally satisfy the entitlement-based checks so those flows keep working.
185 > */
186 > export function chatRequiresSetup(context: IChatSetupRequirement): boolean {
187 return (
188 (!context.completed && !context.hasByokModels) || // Setup not completed (unless BYOK models are available)
197 );
198 }
200 > export interface IChatEntitlementService {
201 >
202 > _serviceBrand: undefined;
203 >
204 > readonly onDidChangeEntitlement: Event<void>;
205 >
206 > readonly entitlement: ChatEntitlement;
207 > readonly entitlementObs: IObservable<ChatEntitlement>;
208 >
209 > readonly clientByokEnabled: boolean;
210 > readonly hasByokModels: boolean;
211 >
212 > readonly organisations: string[] | undefined;
213 > readonly isInternal: boolean;
214 > readonly sku: string | undefined;
215 > readonly copilotTrackingId: string | undefined;
216 >
217 > readonly onDidChangeQuotaExceeded: Event<void>;
218 > readonly onDidChangeQuotaRemaining: Event<void>;
219 > readonly onDidChangeUsageBasedBilling: Event<void>;
220 >
221 > readonly quotas: IQuotas;
222 >
223 > readonly onDidChangeSentiment: Event<void>;
224 >
225 > readonly sentiment: IChatSentiment;
226 > readonly sentimentObs: IObservable<IChatSentiment>;
227 >
228 > // TODO@bpasero eventually this will become enabled by default
229 > // and in that case we only need to check on entitlements change
230 > // between `unknown` and any other entitlement.
231 > readonly onDidChangeAnonymous: Event<void>;
232 > readonly anonymous: boolean;
233 > readonly anonymousObs: IObservable<boolean>;
234 >
235 > acceptQuotas(quotas: IQuotas): void;
236 >
237 > /**
238 > * Clear all quota state.
239 > */
240 > clearQuotas(): void;
241 >
242 > markAnonymousRateLimited(): void;
243 >
244 > /**
245 > * Mark the chat setup flow as completed.
246 > */
247 > markSetupCompleted(): void;
248 >
249 > /**
250 > * Force the hidden state on or off, overriding the normal entitlement logic.
251 > * Used by the account policy gate to hide all AI features when the gate is
252 > * active and unsatisfied.
253 > */
254 > setForceHidden(hidden: boolean): void;
255 >
256 > update(token: CancellationToken): Promise<void>;
257 > }
258 >
259 > //#region Helper Functions
260 >
261 > /**
262 > * Checks the chat entitlements to see if the user falls into the paid category
263 > * @param chatEntitlement The chat entitlement to check
264 > * @returns Whether or not they are a paid user
265 > */
266 > export function isProUser(chatEntitlement: ChatEntitlement): boolean {
267 return chatEntitlement === ChatEntitlement.EDU ||
268 chatEntitlement === ChatEntitlement.Pro ||
272 chatEntitlement === ChatEntitlement.Enterprise;
273 }
275 > /**
276 > * Gets the full plan name for the given chat entitlement
277 > * @param chatEntitlement The chat entitlement to get the plan name for
278 > * @returns The localized full plan name (e.g., "Copilot Pro", "Copilot Free")
279 > */
280 > export function getChatPlanName(chatEntitlement: ChatEntitlement): string {
281 switch (chatEntitlement) {
282 case ChatEntitlement.EDU:
296 }
297 }
299 > //#region Service Implementation
300 >
301 > const defaultChatAgent = {
302 > upgradePlanUrl: product.defaultChatAgent?.upgradePlanUrl ?? '',
303 > providerUriSetting: product.defaultChatAgent?.providerUriSetting ?? '',
304 > entitlementSignupLimitedUrl: product.defaultChatAgent?.entitlementSignupLimitedUrl ?? '',
305 > chatQuotaExceededContext: product.defaultChatAgent?.chatQuotaExceededContext ?? '',
306 > completionsQuotaExceededContext: product.defaultChatAgent?.completionsQuotaExceededContext ?? ''
307 > };
308 >
309 > interface IChatQuotasAccessor {
310 > clearQuotas(): void;
311 > acceptQuotas(quotas: IQuotas): void;
312 > }
313 >
314 > const CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY = 'chat.allowAnonymousAccess';
315 >
316 function isAnonymous(configurationService: IConfigurationService, entitlement: ChatEntitlement, sentiment: IChatSentiment): boolean {
317 if (configurationService.getValue(CHAT_ALLOW_ANONYMOUS_CONFIGURATION_KEY) !== true) {
329 return true;
330 }
332 > type ChatEntitlementClassification = {
333 > owner: 'bpasero';
334 > comment: 'Provides insight into chat entitlements.';
335 > chatHidden: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether chat is hidden or not.' };
336 > chatEntitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current chat entitlement of the user.' };
337 > chatAnonymous: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is anonymously using chat.' };
338 > chatRegistered: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is registered for chat.' };
339 > chatDisabled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether chat is disabled or not.' };
340 > };
341 > type ChatEntitlementEvent = {
342 > chatHidden: boolean;
343 > chatEntitlement: ChatEntitlement;
344 > chatAnonymous: boolean;
345 > chatRegistered: boolean;
346 > chatDisabled: boolean;
347 > };
348 >
349 function logChatEntitlements(state: IChatEntitlementContextState, configurationService: IConfigurationService, telemetryService: ITelemetryService): void {
350 telemetryService.publicLog2<ChatEntitlementEvent, ChatEntitlementClassification>('chatEntitlements', {
356 });
357 }
359 > type ChatAdditionalSpendConfigurationClassification = {
360 > owner: 'pwang347';
361 > comment: 'Tracks when a user enables or disables additional spend.';
362 > enabled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether additional spend is now enabled or disabled.' };
363 > entitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current chat entitlement of the user.' };
364 > };
365 > type ChatAdditionalSpendConfigurationEvent = {
366 > enabled: boolean;
367 > entitlement: ChatEntitlement;
368 > };
369 >
370 > type ChatAdditionalSpendActiveClassification = {
371 > owner: 'pwang347';
372 > comment: 'Tracks when a user enters additional spend (included quota exhausted while additional spend is enabled).';
373 > entitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current chat entitlement of the user.' };
374 > additionalUsageCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of additional spend interactions used so far.' };
375 > };
376 > type ChatAdditionalSpendActiveEvent = {
377 > entitlement: ChatEntitlement;
378 > additionalUsageCount: number;
379 > };
380 >
381 > export class ChatEntitlementService extends Disposable implements IChatEntitlementService {
382 >
383 > declare _serviceBrand: undefined;
384 >
385 > private static readonly CACHED_UBB_STORAGE_KEY = 'chat.usageBasedBilling';
386 >
387 > readonly context: Lazy<ChatEntitlementContext> | undefined;
388 > readonly requests: Lazy<ChatEntitlementRequests> | undefined;
389 >
390 > constructor(
391 @IInstantiationService instantiationService: IInstantiationService,
392 @IProductService productService: IProductService,
467 this.registerListeners();
468 }
470 > //#region --- Entitlements
471 >
472 > readonly onDidChangeEntitlement: Event<void>;
473 > readonly entitlementObs: IObservable<ChatEntitlement>;
474 >
475 > get entitlement(): ChatEntitlement {
476 if (this.contextKeyService.getContextKeyValue<boolean>(ChatEntitlementContextKeys.Entitlement.planEdu.key) === true) {
477 return ChatEntitlement.EDU;
496 return ChatEntitlement.Unresolved;
497 }
499 > get isInternal(): boolean {
500 return this.contextKeyService.getContextKeyValue<boolean>(ChatEntitlementContextKeys.Entitlement.internal.key) === true;
501 }
503 > get organisations(): string[] | undefined {
504 return this.contextKeyService.getContextKeyValue<string[]>(ChatEntitlementContextKeys.Entitlement.organisations.key);
505 }
507 > get sku(): string | undefined {
508 return this.contextKeyService.getContextKeyValue<string>(ChatEntitlementContextKeys.Entitlement.sku.key);
509 }
511 > get copilotTrackingId(): string | undefined {
512 return this.context?.value.state.copilotTrackingId;
513 }
515 > get clientByokEnabled(): boolean {
516 return this.contextKeyService.getContextKeyValue<boolean>('github.copilot.clientByokEnabled') === true;
517 }
519 > get hasByokModels(): boolean {
520 return this.contextKeyService.getContextKeyValue<boolean>('github.copilot.hasByokModels') === true;
521 }
523 > //#endregion
524 >
525 > //#region --- Quotas
526 >
527 > private readonly _onDidChangeQuotaExceeded = this._register(new Emitter<void>());
528 > readonly onDidChangeQuotaExceeded = this._onDidChangeQuotaExceeded.event;
529 >
530 > private readonly _onDidChangeQuotaRemaining = this._register(new Emitter<void>());
531 > readonly onDidChangeQuotaRemaining = this._onDidChangeQuotaRemaining.event;
532 >
533 > private readonly _onDidChangeUsageBasedBilling = this._register(new Emitter<void>());
534 > readonly onDidChangeUsageBasedBilling = this._onDidChangeUsageBasedBilling.event;
535 >
536 > private _quotas: IQuotas;
537 > private quotaCopilotTrackingId: string | undefined;
538 > get quotas() { return this._quotas; }
539 >
540 > private readonly chatQuotaExceededContextKey: IContextKey<boolean>;
541 > private readonly completionsQuotaExceededContextKey: IContextKey<boolean>;
542 >
543 > private ExtensionQuotaContextKeys = {
544 > chatQuotaExceeded: defaultChatAgent.chatQuotaExceededContext,
545 > completionsQuotaExceeded: defaultChatAgent.completionsQuotaExceededContext,
546 > };
547 >
548 > private registerListeners(): void {
549 const quotaExceededSet = new Set([this.ExtensionQuotaContextKeys.chatQuotaExceeded, this.ExtensionQuotaContextKeys.completionsQuotaExceeded]);
550
585 this._register(this.onDidChangeSentiment(() => updateAnonymousUsage()));
586 }
588 > acceptQuotas(incomingQuotas: IQuotas): void {
589 const oldQuota = this._quotas;
590 const cachedQuota = this.quotaCopilotTrackingId === this.copilotTrackingId ? oldQuota : {};
649 }
650 }
652 > private compareQuotas(oldQuota: IQuotaSnapshot | undefined, newQuota: IQuotaSnapshot | undefined): { changed: { exceeded: boolean; remaining: boolean } } {
653 return {
654 changed: {
659 };
660 }
662 > clearQuotas(): void {
663 this.acceptQuotas({});
664 }
666 > private updateContextKeys(): void {
667 const chatExhausted = this._quotas.chat?.percentRemaining === 0;
668 const premiumChatExhausted = this._quotas.premiumChat?.unlimited
677 this.completionsQuotaExceededContextKey.set(this._quotas.completions?.percentRemaining === 0);
678 }
680 > //#endregion
681 >
682 > //#region --- Sentiment
683 >
684 > readonly onDidChangeSentiment: Event<void>;
685 > readonly sentimentObs: IObservable<IChatSentiment>;
686 >
687 > get sentiment(): IChatSentiment {
688 return {
689 completed: this.contextKeyService.getContextKeyValue<boolean>(ChatEntitlementContextKeys.Setup.completed.key) === true,
697 };
698 }
700 > //#endregion
701 >
702 > //region --- Anonymous
703 >
704 > private readonly anonymousContextKey: IContextKey<boolean>;
705 >
706 > private readonly _onDidChangeAnonymous = this._register(new Emitter<void>());
707 > readonly onDidChangeAnonymous = this._onDidChangeAnonymous.event;
708 >
709 > readonly anonymousObs = observableFromEvent(this.onDidChangeAnonymous, () => this.anonymous);
710 >
711 > get anonymous(): boolean {
712 return isAnonymous(this.configurationService, this.entitlement, this.sentiment);
713 }
715 > //#endregion
716 >
717 > markAnonymousRateLimited(): void {
718 if (!this.anonymous) {
719 return;
723 this._onDidChangeQuotaExceeded.fire();
724 }
726 > markSetupCompleted(): void {
727 this.context?.value.update({ completed: true });
728 }
730 > setForceHidden(hidden: boolean): void {
731 if (this.context) {
732 this.context.value.setForceHidden(hidden);
737 }
738 }
740 > async update(token: CancellationToken): Promise<void> {
741 await this.requests?.value.forceResolveEntitlement(token);
742 }
744 >
745 > //#endregion
746 >
747 > //#region Chat Entitlement Request Service
748 >
749 > type EntitlementClassification = {
750 > tid: { classification: 'EndUserPseudonymizedInformation'; purpose: 'BusinessInsight'; comment: 'The anonymized analytics id returned by the service'; endpoint: 'GoogleAnalyticsId' };
751 > entitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flag indicating the chat entitlement state' };
752 > sku: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The SKU of the chat entitlement' };
753 > quotaChatUnlimited: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user has unlimited chat requests' };
754 > quotaChatHasQuota: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user currently has chat quota available' };
755 > quotaChatEntitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The raw chat quota entitlement count' };
756 > quotaPremiumChat: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The percentage of premium chat requests remaining for the user' };
757 > quotaPremiumChatUnlimited: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user has unlimited premium chat requests' };
758 > quotaPremiumChatHasQuota: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user currently has premium chat quota available' };
759 > quotaPremiumChatEntitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The raw premium chat quota entitlement count' };
760 > quotaCompletions: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The percentage of completions remaining for the user' };
761 > quotaCompletionsUnlimited: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user has unlimited completions' };
762 > quotaCompletionsHasQuota: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user currently has completions quota available' };
763 > quotaCompletionsEntitlement: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The raw completions quota entitlement count' };
764 > quotaResetDate: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The date the quota will reset' };
765 > usageBasedBilling: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is on usage-based billing' };
766 > additionalUsageEnabled: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether overage / additional spend is enabled' };
767 > additionalUsageCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of overage interactions used' };
768 > canUpgradePlan: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is eligible to upgrade their plan' };
769 > owner: 'bpasero';
770 > comment: 'Reporting chat entitlements';
771 > };
772 >
773 > type EntitlementEvent = {
774 > entitlement: ChatEntitlement;
775 > tid: string;
776 > sku: string | undefined;
777 > quotaChatUnlimited: boolean | undefined;
778 > quotaChatHasQuota: boolean | undefined;
779 > quotaChatEntitlement: number | undefined;
780 > quotaPremiumChat: number | undefined;
781 > quotaPremiumChatUnlimited: boolean | undefined;
782 > quotaPremiumChatHasQuota: boolean | undefined;
783 > quotaPremiumChatEntitlement: number | undefined;
784 > quotaCompletions: number | undefined;
785 > quotaCompletionsUnlimited: boolean | undefined;
786 > quotaCompletionsHasQuota: boolean | undefined;
787 > quotaCompletionsEntitlement: number | undefined;
788 > quotaResetDate: string | undefined;
789 > usageBasedBilling: boolean | undefined;
790 > additionalUsageEnabled: boolean | undefined;
791 > additionalUsageCount: number | undefined;
792 > canUpgradePlan: boolean | undefined;
793 > };
794 >
795 > interface IEntitlements {
796 > readonly entitlement: ChatEntitlement;
797 > readonly organisations?: string[];
798 > readonly sku?: string;
799 > readonly copilotTrackingId?: string;
800 > readonly quotas?: IQuotas;
801 > }
802 >
803 > export interface IQuotaSnapshot {
804 > readonly percentRemaining: number;
805 > readonly unlimited: boolean;
806 > readonly hasQuota?: boolean;
807 > readonly resetAt?: number;
808 > readonly usageBasedBilling?: boolean;
809 > readonly entitlement?: number;
810 > readonly quotaRemaining?: number;
811 > readonly creditsUsed?: number;
812 > }
813 >
814 > export interface IRateLimitSnapshot {
815 > readonly percentRemaining: number;
816 > readonly unlimited: boolean;
817 > readonly resetDate?: string;
818 > }
819 >
820 > interface IQuotas {
821 > readonly resetDate?: string;
822 > readonly resetDateHasTime?: boolean;
823 >
824 > readonly usageBasedBilling?: boolean;
825 > readonly canUpgradePlan?: boolean;
826 >
827 > readonly chat?: IQuotaSnapshot;
828 > readonly completions?: IQuotaSnapshot;
829 > readonly premiumChat?: IQuotaSnapshot;
830 > readonly additionalUsageEnabled?: boolean;
831 > readonly additionalUsageCount?: number;
832 > readonly additionalUsageEntitlement?: number;
833 >
834 > readonly sessionRateLimit?: IRateLimitSnapshot;
835 > readonly weeklyRateLimit?: IRateLimitSnapshot;
836 > }
837 >
838 function mergeDefinedSnapshot<T extends object>(previous: T | undefined, current: T): T {
839 const result = { ...previous, ...current };
845 return result;
846 }
848 > export function parseQuotas(entitlementsData: IEntitlementsData): IQuotas {
849 const quotas: Mutable<IQuotas> = {
850 resetDate: entitlementsData.quota_reset_date_utc ?? entitlementsData.quota_reset_date ?? entitlementsData.limited_user_reset_date,
919 return quotas;
920 }
922 > export class ChatEntitlementRequests extends Disposable {
923 >
924 > private state: IEntitlements;
925 >
926 > private pendingResolveCts = new CancellationTokenSource();
927 >
928 > constructor(
929 private readonly context: ChatEntitlementContext,
930 private readonly chatQuotasAccessor: IChatQuotasAccessor,
946 this.resolve();
947 }
949 > private registerListeners(): void {
950 this._register(this.defaultAccountService.onDidChangeDefaultAccount(() => this.resolve()));
951
959 }));
960 }
962 > private async resolve(): Promise<void> {
963 this.pendingResolveCts.dispose(true);
964 const cts = this.pendingResolveCts = new CancellationTokenSource();
989 }
990 }
992 > private async resolveEntitlement(defaultAccount: IDefaultAccount, token: CancellationToken): Promise<IEntitlements | undefined> {
993 const entitlements = await this.doResolveEntitlement(defaultAccount, token);
994 if (typeof entitlements?.entitlement === 'number' && !token.isCancellationRequested) {
997 return entitlements;
998 }
1000 > private async doResolveEntitlement(defaultAccount: IDefaultAccount, token: CancellationToken): Promise<IEntitlements | undefined> {
1001 if (token.isCancellationRequested) {
1002 return undefined;
1065 return entitlements;
1066 }
1068 > private toQuotas(entitlementsData: IEntitlementsData): IQuotas {
1069 return parseQuotas(entitlementsData);
1070 }
1072 > private async request(url: string, type: 'GET', body: undefined, sessions: AuthenticationSession[], token: CancellationToken, callSite: string): Promise<IRequestContext | undefined>;
1073 > private async request(url: string, type: 'POST', body: object, sessions: AuthenticationSession[], token: CancellationToken, callSite: string): Promise<IRequestContext | undefined>;
1074 > private async request(url: string, type: 'GET' | 'POST', body: object | undefined, sessions: AuthenticationSession[], token: CancellationToken, callSite: string): Promise<IRequestContext | undefined> {
1075 let lastRequest: IRequestContext | undefined;
1076
1108 return lastRequest;
1109 }
1111 > private update(state: IEntitlements): void {
1112 this.state = state;
1113
1118 }
1119 }
1121 > async forceResolveEntitlement(token = CancellationToken.None): Promise<IEntitlements | undefined> {
1122 const defaultAccount = await this.defaultAccountService.refresh({ forceRefresh: true });
1123 if (!defaultAccount) {
1127 return this.resolveEntitlement(defaultAccount, token);
1128 }
1130 > async signUpFree(): Promise<true /* signed up */ | false /* already signed up */ | { errorCode: number } /* error */ | undefined /* no session */> {
1131 const sessions = await this.getSessions();
1132 if (sessions.length === 0) {
1135 return this.doSignUpFree(sessions);
1136 }
1138 > private async doSignUpFree(sessions: AuthenticationSession[]): Promise<true /* signed up */ | false /* already signed up */ | { errorCode: number } /* error */> {
1139 const body = {
1140 restricted_telemetry: this.telemetryService.telemetryLevel === TelemetryLevel.NONE ? 'disabled' : 'enabled',
1194 return Boolean(parsedResult?.subscribed);
1195 }
1197 > private async getSessions(): Promise<AuthenticationSession[]> {
1198 const defaultAccount = await this.defaultAccountService.getDefaultAccount();
1199 if (defaultAccount) {
1206 return [...(await this.authenticationService.getSessions(this.defaultAccountService.getDefaultAccountAuthenticationProvider().id))];
1207 }
1209 > private async onUnknownSignUpError(detail: string, logMessage: string): Promise<boolean> {
1210 this.logService.error(logMessage);
1211
1223 return false;
1224 }
1226 > private onUnprocessableSignUpError(logMessage: string, logDetails: string): void {
1227 this.logService.error(logMessage);
1228
1245 }
1246 }
1248 > async signIn(options?: { useSocialProvider?: string; additionalScopes?: readonly string[] }): Promise<{ defaultAccount?: IDefaultAccount; entitlements?: IEntitlements }> {
1249 const defaultAccount = await this.defaultAccountService.signIn({
1250 additionalScopes: options?.additionalScopes,
1259 return { defaultAccount, entitlements };
1260 }
1262 > override dispose(): void {
1263 this.pendingResolveCts.dispose(true);
1264
1265 super.dispose();
1266 }
1268 >
1269 > //#endregion
1270 >
1271 > //#region Context
1272 >
1273 > export interface IChatEntitlementContextState extends IChatSentiment {
1274 >
1275 > /**
1276 > * Users last known or resolved entitlement.
1277 > */
1278 > entitlement: ChatEntitlement;
1279 >
1280 > /**
1281 > * User's last known or resolved raw SKU type.
1282 > */
1283 > sku: string | undefined;
1284 >
1285 > /**
1286 > * User's last known or resolved organisations.
1287 > */
1288 > organisations: string[] | undefined;
1289 >
1290 > /**
1291 > * User's Copilot tracking ID from the entitlement API.
1292 > */
1293 > copilotTrackingId: string | undefined;
1294 > }
1295 >
1296 > export class ChatEntitlementContext extends Disposable {
1297 >
1298 > private static readonly CHAT_ENTITLEMENT_CONTEXT_STORAGE_KEY = 'chat.setupContext';
1299 > private static readonly CHAT_ENTITLEMENT_CONTEXT_MIGRATED_STORAGE_KEY = 'chat.setupContext.migrated.v1';
1300 >
1301 > private static readonly CHAT_DISABLED_CONFIGURATION_KEY = 'chat.disableAIFeatures';
1302 >
1303 > private readonly canSignUpContextKey: IContextKey<boolean>;
1304 > private readonly signedOutContextKey: IContextKey<boolean>;
1305 >
1306 > private readonly freeContextKey: IContextKey<boolean>;
1307 > private readonly eduContextKey: IContextKey<boolean>;
1308 > private readonly proContextKey: IContextKey<boolean>;
1309 > private readonly proPlusContextKey: IContextKey<boolean>;
1310 > private readonly maxContextKey: IContextKey<boolean>;
1311 > private readonly businessContextKey: IContextKey<boolean>;
1312 > private readonly enterpriseContextKey: IContextKey<boolean>;
1313 >
1314 > private readonly organisationsContextKey: IContextKey<string[] | undefined>;
1315 > private readonly isInternalContextKey: IContextKey<boolean>;
1316 > private readonly skuContextKey: IContextKey<string | undefined>;
1317 >
1318 > private readonly completedContext: IContextKey<boolean>;
1319 > private readonly hiddenContext: IContextKey<boolean>;
1320 > private readonly disabledInWorkspaceContext: IContextKey<boolean>;
1321 > private readonly laterContext: IContextKey<boolean>;
1322 > private readonly installedContext: IContextKey<boolean>;
1323 > private readonly disabledContext: IContextKey<boolean>;
1324 > private readonly untrustedContext: IContextKey<boolean>;
1325 > private readonly registeredContext: IContextKey<boolean>;
1326 >
1327 > private _state: IChatEntitlementContextState;
1328 > private suspendedState: IChatEntitlementContextState | undefined = undefined;
1329 > get state(): IChatEntitlementContextState { return this.withConfiguration(this.suspendedState ?? this._state); }
1330 >
1331 > private readonly _onDidChange = this._register(new Emitter<void>());
1332 > readonly onDidChange = this._onDidChange.event;
1333 >
1334 > private updateBarrier: Barrier | undefined = undefined;
1335 >
1336 > constructor(
1337 @IContextKeyService contextKeyService: IContextKeyService,
1338 @IStorageService private readonly storageService: IStorageService,
1387 this.registerListeners();
1388 }
1390 > private registerListeners(): void {
1391 this._register(this.configurationService.onDidChangeConfiguration(e => {
1392 if (e.affectsConfiguration(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY)) {
1395 }));
1396 }
1398 > private _forceHidden = false;
1399 >
1400 > private withConfiguration(state: IChatEntitlementContextState): IChatEntitlementContextState {
1401 if (this._forceHidden || this.configurationService.getValue(ChatEntitlementContext.CHAT_DISABLED_CONFIGURATION_KEY) === true) {
1402 return {
1408 return state;
1409 }
1411 > setForceHidden(hidden: boolean): void {
1412 if (this._forceHidden !== hidden) {
1413 this._forceHidden = hidden;
1415 }
1416 }
1418 > update(context: { installed: boolean; disabled: boolean; untrusted: boolean; disabledInWorkspace: boolean }): Promise<void>;
1419 > update(context: { completed: true }): Promise<void>;
1420 > update(context: { hidden: false }): Promise<void>; // legacy UI state from before we had a setting to hide, keep around to still support users who used this
1421 > update(context: { later: boolean }): Promise<void>;
1422 > update(context: { entitlement: ChatEntitlement; organisations: string[] | undefined; sku: string | undefined; copilotTrackingId: string | undefined }): Promise<void>;
1423 > async update(context: { completed?: boolean; installed?: boolean; disabled?: boolean; untrusted?: boolean; disabledInWorkspace?: boolean; hidden?: false; later?: boolean; entitlement?: ChatEntitlement; organisations?: string[]; sku?: string; copilotTrackingId?: string }): Promise<void> {
1424 this.logService.trace(`[chat entitlement context] update(): ${JSON.stringify(context)}`);
1425
1477 return this.updateContext();
1478 }
1480 > private async updateContext(): Promise<void> {
1481 await this.updateBarrier?.wait();
1482
1483 this.updateContextSync();
1484 }
1486 > private updateContextSync(): void {
1487 const state = this.withConfiguration(this._state);
1488
1516 this._onDidChange.fire();
1517 }
1519 > suspend(): void {
1520 this.suspendedState = { ...this._state };
1521 this.updateBarrier = new Barrier();
1522 }
1524 > resume(): void {
1525 this.suspendedState = undefined;
1526 this.updateBarrier?.open();
1527 this.updateBarrier = undefined;
1528 }
1530 >
1531 > //#endregion
1532 >
1533 > registerSingleton(IChatEntitlementService, ChatEntitlementService, InstantiationType.Eager /* To ensure context keys are set asap */);
src/vs/workbench/services/authentication/common/authentication.ts 507 introduced LOC · 4 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- authentication.ts
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 { Event } from '../../../../base/common/event.js';
6 > import { IDisposable } from '../../../../base/common/lifecycle.js';
7 > import { IAuthenticationChallenge, IAuthorizationProtectedResourceMetadata, IAuthorizationServerMetadata } from '../../../../base/common/oauth.js';
8 > import { URI } from '../../../../base/common/uri.js';
9 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
10 >
11 > /**
12 > * Use this if you don't want the onDidChangeSessions event to fire in the extension host
13 > */
14 > export const INTERNAL_AUTH_PROVIDER_PREFIX = '__';
15 >
16 > export interface AuthenticationSessionAccount {
17 > label: string;
18 > id: string;
19 > }
20 >
21 > export interface AuthenticationSession {
22 > id: string;
23 > accessToken: string;
24 > account: AuthenticationSessionAccount;
25 > scopes: ReadonlyArray<string>;
26 > idToken?: string;
27 > }
28 >
29 > export interface AuthenticationSessionsChangeEvent {
30 > added: ReadonlyArray<AuthenticationSession> | undefined;
31 > removed: ReadonlyArray<AuthenticationSession> | undefined;
32 > changed: ReadonlyArray<AuthenticationSession> | undefined;
33 > }
34 >
35 > export interface AuthenticationProviderInformation {
36 > id: string;
37 > label: string;
38 > authorizationServerGlobs?: ReadonlyArray<string>;
39 > }
40 >
41 > /**
42 > * Options for creating an authentication session via the service.
43 > */
44 > export interface IAuthenticationCreateSessionOptions {
45 > activateImmediate?: boolean;
46 > /**
47 > * The account that is being asked about. If this is passed in, the provider should
48 > * attempt to return the sessions that are only related to this account.
49 > */
50 > account?: AuthenticationSessionAccount;
51 > /**
52 > * The authorization server URI to use for this creation request. If passed in, first we validate that
53 > * the provider can use this authorization server, then it is passed down to the auth provider.
54 > */
55 > authorizationServer?: URI;
56 > /**
57 > * When specified, the authentication provider will request a token bound to this resource URI
58 > * (RFC 8707 resource indicator).
59 > */
60 > resource?: string;
61 > /**
62 > * The audience for the requested access token. Primarily used for OAuth Identity Assertion
63 > * Authorization Grant (ID-JAG, defined in `draft-ietf-oauth-identity-assertion-authz-grant` using RFC 8693 token-exchange semantics) flows where the audience identifies the authorization server of the resource that
64 > * will redeem the assertion (typically the resource's authorization server URL). Providers that do not understand audience-bound tokens should
65 > * ignore this option.
66 > */
67 > audience?: string;
68 > /**
69 > * Allows the authentication provider to take in additional parameters.
70 > * It is up to the provider to define what these parameters are and handle them.
71 > * This is useful for passing in additional information that is specific to the provider
72 > * and not part of the standard authentication flow.
73 > */
74 > [key: string]: any;
75 > }
76 >
77 > export interface IAuthenticationWwwAuthenticateRequest {
78 > /**
79 > * The raw WWW-Authenticate header value that triggered this challenge.
80 > * This will be parsed by the authentication provider to extract the necessary
81 > * challenge information.
82 > */
83 > readonly wwwAuthenticate: string;
84 >
85 > /**
86 > * Optional scopes for the session. If not provided, the authentication provider
87 > * may use default scopes or extract them from the challenge.
88 > */
89 > readonly fallbackScopes?: readonly string[];
90 > }
91 >
92 > export function isAuthenticationWwwAuthenticateRequest(obj: unknown): obj is IAuthenticationWwwAuthenticateRequest {
93 return typeof obj === 'object'
94 && obj !== null
96 && (typeof obj.wwwAuthenticate === 'string');
97 }
99 > /**
100 > * Represents constraints for authentication, including challenges and optional scopes.
101 > * This is used when creating or retrieving sessions that must satisfy specific authentication
102 > * requirements from WWW-Authenticate headers.
103 > */
104 > export interface IAuthenticationConstraint {
105 > /**
106 > * Array of authentication challenges parsed from WWW-Authenticate headers.
107 > */
108 > readonly challenges: readonly IAuthenticationChallenge[];
109 >
110 > /**
111 > * Optional scopes for the session. If not provided, the authentication provider
112 > * may extract scopes from the challenges or use default scopes.
113 > */
114 > readonly fallbackScopes?: readonly string[];
115 > }
116 >
117 > /**
118 > * Options for getting authentication sessions via the service.
119 > */
120 > export interface IAuthenticationGetSessionsOptions {
121 > /**
122 > * Whether the provider must avoid user interaction while resolving existing sessions.
123 > */
124 > silent?: boolean;
125 > /**
126 > * The account that is being asked about. If this is passed in, the provider should
127 > * attempt to return the sessions that are only related to this account.
128 > */
129 > account?: AuthenticationSessionAccount;
130 > /**
131 > * The authorization server URI to use for this request. If passed in, first we validate that
132 > * the provider can use this authorization server, then it is passed down to the auth provider.
133 > */
134 > authorizationServer?: URI;
135 > /**
136 > * When specified, the authentication provider will request a token bound to this resource URI
137 > * (RFC 8707 resource indicator).
138 > */
139 > resource?: string;
140 > /**
141 > * The audience for the requested access token. Primarily used for OAuth Identity Assertion
142 > * Authorization Grant (ID-JAG, defined in `draft-ietf-oauth-identity-assertion-authz-grant` using RFC 8693 token-exchange semantics) flows where the audience identifies the authorization server of the resource that
143 > * will redeem the assertion (typically the resource's authorization server URL). Providers that do not understand audience-bound tokens should
144 > * ignore this option.
145 > */
146 > audience?: string;
147 > /**
148 > * Allows the authentication provider to take in additional parameters.
149 > * It is up to the provider to define what these parameters are and handle them.
150 > * This is useful for passing in additional information that is specific to the provider
151 > * and not part of the standard authentication flow.
152 > */
153 > [key: string]: any;
154 > }
155 >
156 > export interface AllowedExtension {
157 > id: string;
158 > name: string;
159 > /**
160 > * If true or undefined, the extension is allowed to use the account
161 > * If false, the extension is not allowed to use the account
162 > * TODO: undefined shouldn't be a valid value, but it is for now
163 > */
164 > allowed?: boolean;
165 > lastUsed?: number;
166 > // If true, this comes from the product.json
167 > trusted?: boolean;
168 > }
169 >
170 > export interface IAuthenticationProviderHostDelegate {
171 > /** Priority for this delegate, delegates are tested in descending priority order */
172 > readonly priority: number;
173 > create(authorizationServer: URI, serverMetadata: IAuthorizationServerMetadata, resource: IAuthorizationProtectedResourceMetadata | undefined, clientId?: string, clientSecret?: string): Promise<string>;
174 > /**
175 > * Creates an XAA (enterprise-managed, ID-JAG) authentication provider for the given SSO issuer.
176 > * The returned string is the provider id.
177 > */
178 > createXaa?(issuer: URI): Promise<string>;
179 > }
180 >
181 > export function getDynamicAuthenticationProviderId(authorizationServer: URI, resource: IAuthorizationProtectedResourceMetadata | undefined): string {
182 return resource ? `${authorizationServer.toString(true)} ${resource.resource}` : authorizationServer.toString(true);
183 }
185 > export const IAuthenticationService = createDecorator<IAuthenticationService>('IAuthenticationService');
186 >
187 > export interface IAuthenticationService {
188 > readonly _serviceBrand: undefined;
189 >
190 > /**
191 > * Fires when an authentication provider has been registered
192 > */
193 > readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
194 > /**
195 > * Fires when an authentication provider has been unregistered
196 > */
197 > readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
198 >
199 > /**
200 > * Fires when the list of sessions for a provider has been added, removed or changed
201 > */
202 > readonly onDidChangeSessions: Event<{ providerId: string; label: string; event: AuthenticationSessionsChangeEvent }>;
203 >
204 > /**
205 > * Fires when the list of declaredProviders has changed
206 > */
207 > readonly onDidChangeDeclaredProviders: Event<void>;
208 >
209 > /**
210 > * All providers that have been statically declared by extensions. These may not actually be registered or active yet.
211 > */
212 > readonly declaredProviders: AuthenticationProviderInformation[];
213 >
214 > /**
215 > * Registers that an extension has declared an authentication provider in their package.json
216 > * @param provider The provider information to register
217 > */
218 > registerDeclaredAuthenticationProvider(provider: AuthenticationProviderInformation): void;
219 >
220 > /**
221 > * Unregisters a declared authentication provider
222 > * @param id The id of the provider to unregister
223 > */
224 > unregisterDeclaredAuthenticationProvider(id: string): void;
225 >
226 > /**
227 > * Checks if an authentication provider has been registered
228 > * @param id The id of the provider to check
229 > */
230 > isAuthenticationProviderRegistered(id: string): boolean;
231 >
232 > /**
233 > * Checks if an authentication provider is dynamic
234 > * @param id The id of the provider to check
235 > */
236 > isDynamicAuthenticationProvider(id: string): boolean;
237 >
238 > /**
239 > * Registers an authentication provider
240 > * @param id The id of the provider
241 > * @param provider The implementation of the provider
242 > */
243 > registerAuthenticationProvider(id: string, provider: IAuthenticationProvider): void;
244 >
245 > /**
246 > * Unregisters an authentication provider
247 > * @param id The id of the provider to unregister
248 > */
249 > unregisterAuthenticationProvider(id: string): void;
250 >
251 > /**
252 > * Gets the provider ids of all registered authentication providers
253 > */
254 > getProviderIds(): string[];
255 >
256 > /**
257 > * Gets the provider with the given id.
258 > * @param id The id of the provider to get
259 > * @throws if the provider is not registered
260 > */
261 > getProvider(id: string): IAuthenticationProvider;
262 >
263 > /**
264 > * Gets all accounts that are currently logged in across all sessions
265 > * @param id The id of the provider to ask for accounts
266 > * @returns A promise that resolves to an array of accounts
267 > */
268 > getAccounts(id: string): Promise<ReadonlyArray<AuthenticationSessionAccount>>;
269 >
270 > /**
271 > * Gets all sessions that satisfy the given scopes from the provider with the given id
272 > * @param id The id of the provider to ask for a session
273 > * @param scopes The scopes for the session
274 > * @param options Additional options for getting sessions
275 > * @param activateImmediate If true, the provider should activate immediately if it is not already
276 > */
277 > getSessions(id: string, scopeListOrRequest?: ReadonlyArray<string> | IAuthenticationWwwAuthenticateRequest, options?: IAuthenticationGetSessionsOptions, activateImmediate?: boolean): Promise<ReadonlyArray<AuthenticationSession>>;
278 >
279 > /**
280 > * Creates an AuthenticationSession with the given provider and scopes
281 > * @param providerId The id of the provider
282 > * @param scopes The scopes to request
283 > * @param options Additional options for creating the session
284 > */
285 > createSession(providerId: string, scopeListOrRequest: ReadonlyArray<string> | IAuthenticationWwwAuthenticateRequest, options?: IAuthenticationCreateSessionOptions): Promise<AuthenticationSession>;
286 >
287 > /**
288 > * Removes the session with the given id from the provider with the given id
289 > * @param providerId The id of the provider
290 > * @param sessionId The id of the session to remove
291 > */
292 > removeSession(providerId: string, sessionId: string): Promise<void>;
293 >
294 > /**
295 > * Gets a provider id for a specified authorization server
296 > * @param authorizationServer The authorization server url that this provider is responsible for
297 > * @param resourceServer The resource server URI that should match the provider's resourceServer (if defined)
298 > */
299 > getOrActivateProviderIdForServer(authorizationServer: URI, resourceServer?: URI): Promise<string | undefined>;
300 >
301 > /**
302 > * Allows the ability register a delegate that will be used to start authentication providers
303 > * @param delegate The delegate to register
304 > */
305 > registerAuthenticationProviderHostDelegate(delegate: IAuthenticationProviderHostDelegate): IDisposable;
306 >
307 > /**
308 > * Creates a dynamic authentication provider for the given server metadata
309 > * @param serverMetadata The metadata for the server that is being authenticated against
310 > */
311 > createDynamicAuthenticationProvider(authorizationServer: URI, serverMetadata: IAuthorizationServerMetadata, resourceMetadata: IAuthorizationProtectedResourceMetadata | undefined, clientId?: string, clientSecret?: string): Promise<IAuthenticationProvider | undefined>;
312 >
313 > /**
314 > * Gets or creates a built-in XAA (enterprise-managed, ID-JAG) authentication provider for the given
315 > * SSO issuer. Subsequent calls with the same issuer return the existing provider. The returned id
316 > * can be used with {@link getSessions}/{@link createSession} just like any other provider.
317 > *
318 > * @param issuer The OAuth/OIDC issuer URL (typically read from `mcp.enterpriseManagedAuth.idp`).
319 > */
320 > createOrGetXaaProvider(issuer: URI): Promise<string | undefined>;
321 > }
322 >
323 > export function isAuthenticationSession(thing: unknown): thing is AuthenticationSession {
324 if (typeof thing !== 'object' || !thing) {
325 return false;
349 return true;
350 }
352 > // TODO: Move this into MainThreadAuthentication
353 > export const IAuthenticationExtensionsService = createDecorator<IAuthenticationExtensionsService>('IAuthenticationExtensionsService');
354 > export interface IAuthenticationExtensionsService {
355 > readonly _serviceBrand: undefined;
356 >
357 > /**
358 > * Fires when an account preference for a specific provider has changed for the specified extensions. Does not fire when:
359 > * * An account preference is removed
360 > * * A session preference is changed (because it's deprecated)
361 > * * A session preference is removed (because it's deprecated)
362 > */
363 > readonly onDidChangeAccountPreference: Event<{ extensionIds: string[]; providerId: string }>;
364 > /**
365 > * Returns the accountName (also known as account.label) to pair with `IAuthenticationAccessService` to get the account preference
366 > * @param providerId The authentication provider id
367 > * @param extensionId The extension id to get the preference for
368 > * @returns The accountName of the preference, or undefined if there is no preference set
369 > */
370 > getAccountPreference(extensionId: string, providerId: string): string | undefined;
371 > /**
372 > * Sets the account preference for the given provider and extension
373 > * @param providerId The authentication provider id
374 > * @param extensionId The extension id to set the preference for
375 > * @param account The account to set the preference to
376 > */
377 > updateAccountPreference(extensionId: string, providerId: string, account: AuthenticationSessionAccount): void;
378 > /**
379 > * Removes the account preference for the given provider and extension
380 > * @param providerId The authentication provider id
381 > * @param extensionId The extension id to remove the preference for
382 > */
383 > removeAccountPreference(extensionId: string, providerId: string): void;
384 > /**
385 > * @deprecated Sets the session preference for the given provider and extension
386 > * @param providerId
387 > * @param extensionId
388 > * @param session
389 > */
390 > updateSessionPreference(providerId: string, extensionId: string, session: AuthenticationSession): void;
391 > /**
392 > * @deprecated Gets the session preference for the given provider and extension
393 > * @param providerId
394 > * @param extensionId
395 > * @param scopes
396 > */
397 > getSessionPreference(providerId: string, extensionId: string, scopes: string[]): string | undefined;
398 > /**
399 > * @deprecated Removes the session preference for the given provider and extension
400 > * @param providerId
401 > * @param extensionId
402 > * @param scopes
403 > */
404 > removeSessionPreference(providerId: string, extensionId: string, scopes: string[]): void;
405 > selectSession(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray<string> | IAuthenticationWwwAuthenticateRequest, possibleSessions: readonly AuthenticationSession[]): Promise<AuthenticationSession>;
406 > requestSessionAccess(providerId: string, extensionId: string, extensionName: string, scopeListOrRequest: ReadonlyArray<string> | IAuthenticationWwwAuthenticateRequest, possibleSessions: readonly AuthenticationSession[]): void;
407 > requestNewSession(providerId: string, scopeListOrRequest: ReadonlyArray<string> | IAuthenticationWwwAuthenticateRequest, extensionId: string, extensionName: string): Promise<void>;
408 > updateNewSessionRequests(providerId: string, addedSessions: readonly AuthenticationSession[]): void;
409 > }
410 >
411 > /**
412 > * Options passed to the authentication provider when asking for sessions.
413 > */
414 > export interface IAuthenticationProviderSessionOptions {
415 > /**
416 > * Whether the provider must avoid user interaction while resolving existing sessions.
417 > */
418 > silent?: boolean;
419 > /**
420 > * The account that is being asked about. If this is passed in, the provider should
421 > * attempt to return the sessions that are only related to this account.
422 > */
423 > account?: AuthenticationSessionAccount;
424 > /**
425 > * The authorization server that is being asked about. If this is passed in, the provider should
426 > * attempt to return sessions that are only related to this authorization server.
427 > */
428 > authorizationServer?: URI;
429 > /**
430 > * When specified, the authentication provider will request a token bound to this resource URI
431 > * (RFC 8707 resource indicator).
432 > */
433 > resource?: string;
434 > /**
435 > * The audience for the requested access token. Primarily used for OAuth Identity Assertion
436 > * Authorization Grant (ID-JAG, defined in `draft-ietf-oauth-identity-assertion-authz-grant` using RFC 8693 token-exchange semantics) flows where the audience identifies the authorization server of the resource that
437 > * will redeem the assertion (typically the resource's authorization server URL). Providers that do not understand audience-bound tokens should
438 > * ignore this option.
439 > */
440 > audience?: string;
441 > /**
442 > * Allows the authentication provider to take in additional parameters.
443 > * It is up to the provider to define what these parameters are and handle them.
444 > * This is useful for passing in additional information that is specific to the provider
445 > * and not part of the standard authentication flow.
446 > */
447 > [key: string]: any;
448 > }
449 >
450 > /**
451 > * Represents an authentication provider.
452 > */
453 > export interface IAuthenticationProvider {
454 > /**
455 > * The unique identifier of the authentication provider.
456 > */
457 > readonly id: string;
458 >
459 > /**
460 > * The display label of the authentication provider.
461 > */
462 > readonly label: string;
463 >
464 > /**
465 > * The resource server URI that this provider is responsible for, if any.
466 > * TODO@TylerLeonhardt: Rather than this being added to the provider, it should be passed in to
467 > * getSessions/createSession/etc... this way we can have providers that handle multiple resource servers.
468 > */
469 > readonly resourceServer?: URI;
470 >
471 > /**
472 > * The resolved authorization servers. These can still contain globs, but should be concrete URIs
473 > */
474 > readonly authorizationServers?: ReadonlyArray<URI>;
475 >
476 > /**
477 > * Indicates whether the authentication provider supports multiple accounts.
478 > */
479 > readonly supportsMultipleAccounts: boolean;
480 >
481 > /**
482 > * Optional function to provide a custom confirmation message for authentication prompts.
483 > * If not implemented, the default confirmation messages will be used.
484 > * @param extensionName - The name of the extension requesting authentication.
485 > * @param recreatingSession - Whether this is recreating an existing session.
486 > * @returns A custom confirmation message or undefined to use the default message.
487 > */
488 > readonly confirmation?: (extensionName: string, recreatingSession: boolean) => string | undefined;
489 >
490 > /**
491 > * An {@link Event} which fires when the array of sessions has changed, or data
492 > * within a session has changed.
493 > */
494 > readonly onDidChangeSessions: Event<AuthenticationSessionsChangeEvent>;
495 >
496 > /**
497 > * Retrieves a list of authentication sessions.
498 > * @param scopes - An optional list of scopes. If provided, the sessions returned should match these permissions, otherwise all sessions should be returned.
499 > * @param options - Additional options for getting sessions.
500 > * @returns A promise that resolves to an array of authentication sessions.
501 > */
502 > getSessions(scopes: string[] | undefined, options: IAuthenticationProviderSessionOptions): Promise<readonly AuthenticationSession[]>;
503 >
504 > /**
505 > * Prompts the user to log in.
506 > * If login is successful, the `onDidChangeSessions` event should be fired.
507 > * If login fails, a rejected promise should be returned.
508 > * If the provider does not support multiple accounts, this method should not be called if there is already an existing session matching the provided scopes.
509 > * @param scopes - A list of scopes that the new session should be created with.
510 > * @param options - Additional options for creating the session.
511 > * @returns A promise that resolves to an authentication session.
512 > */
513 > createSession(scopes: string[], options: IAuthenticationProviderSessionOptions): Promise<AuthenticationSession>;
514 >
515 > /**
516 > * Get existing sessions that match the given authentication constraints.
517 > *
518 > * @param constraint The authentication constraint containing challenges and optional scopes
519 > * @param options Options for the session request
520 > * @returns A thenable that resolves to an array of existing authentication sessions
521 > */
522 > getSessionsFromChallenges?(constraint: IAuthenticationConstraint, options: IAuthenticationProviderSessionOptions): Promise<readonly AuthenticationSession[]>;
523 >
524 > /**
525 > * Create a new session based on authentication constraints.
526 > * This is called when no existing session matches the constraint requirements.
527 > *
528 > * @param constraint The authentication constraint containing challenges and optional scopes
529 > * @param options Options for the session creation
530 > * @returns A thenable that resolves to a new authentication session
531 > */
532 > createSessionFromChallenges?(constraint: IAuthenticationConstraint, options: IAuthenticationProviderSessionOptions): Promise<AuthenticationSession>;
533 >
534 > /**
535 > * Removes the session corresponding to the specified session ID.
536 > * If the removal is successful, the `onDidChangeSessions` event should be fired.
537 > * If a session cannot be removed, the provider should reject with an error message.
538 > * @param sessionId - The ID of the session to remove.
539 > */
540 > removeSession(sessionId: string): Promise<void>;
541 > }
src/vs/workbench/services/lifecycle/common/lifecycle.ts 292 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- lifecycle.ts
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 >
6 > import { CancellationToken } from '../../../../base/common/cancellation.js';
7 > import { Event } from '../../../../base/common/event.js';
8 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
9 >
10 > export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
11 >
12 > /**
13 > * An event that is send out when the window is about to close. Clients have a chance to veto
14 > * the closing by either calling veto with a boolean "true" directly or with a promise that
15 > * resolves to a boolean. Returning a promise is useful in cases of long running operations
16 > * on shutdown.
17 > *
18 > * Note: It is absolutely important to avoid long running promises if possible. Please try hard
19 > * to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
20 > */
21 > export interface BeforeShutdownEvent {
22 >
23 > /**
24 > * The reason why the application will be shutting down.
25 > */
26 > readonly reason: ShutdownReason;
27 >
28 > /**
29 > * Allows to veto the shutdown. The veto can be a long running operation but it
30 > * will block the application from closing.
31 > *
32 > * @param id to identify the veto operation in case it takes very long or never
33 > * completes.
34 > */
35 > veto(value: boolean | Promise<boolean>, id: string): void;
36 > }
37 >
38 > export interface InternalBeforeShutdownEvent extends BeforeShutdownEvent {
39 >
40 > /**
41 > * Allows to set a veto operation to run after all other
42 > * vetos have been handled from the `BeforeShutdownEvent`
43 > *
44 > * This method is hidden from the API because it is intended
45 > * to be only used once internally.
46 > */
47 > finalVeto(vetoFn: () => boolean | Promise<boolean>, id: string): void;
48 > }
49 >
50 > /**
51 > * An event that signals an error happened during `onBeforeShutdown` veto handling.
52 > * In this case the shutdown operation will not proceed because this is an unexpected
53 > * condition that is treated like a veto.
54 > */
55 > export interface BeforeShutdownErrorEvent {
56 >
57 > /**
58 > * The reason why the application is shutting down.
59 > */
60 > readonly reason: ShutdownReason;
61 >
62 > /**
63 > * The error that happened during shutdown handling.
64 > */
65 > readonly error: Error;
66 > }
67 >
68 > export enum WillShutdownJoinerOrder {
69 >
70 > /**
71 > * Joiners to run before the `Last` joiners. This is the default order and best for
72 > * most cases. You can be sure that services are still functional at this point.
73 > */
74 > Default = 1,
75 >
76 > /**
77 > * The joiners to run last. This should ONLY be used in rare cases when you have no
78 > * dependencies to workbench services or state. The workbench may be in a state where
79 > * resources can no longer be accessed or changed.
80 > */
81 > Last
82 > }
83 >
84 > export interface IWillShutdownEventJoiner {
85 > readonly id: string;
86 > readonly label: string;
87 > readonly order?: WillShutdownJoinerOrder;
88 > }
89 >
90 > export interface IWillShutdownEventDefaultJoiner extends IWillShutdownEventJoiner {
91 > readonly order?: WillShutdownJoinerOrder.Default;
92 > }
93 >
94 > export interface IWillShutdownEventLastJoiner extends IWillShutdownEventJoiner {
95 > readonly order: WillShutdownJoinerOrder.Last;
96 > }
97 >
98 > /**
99 > * An event that is send out when the window closes. Clients have a chance to join the closing
100 > * by providing a promise from the join method. Returning a promise is useful in cases of long
101 > * running operations on shutdown.
102 > *
103 > * Note: It is absolutely important to avoid long running promises if possible. Please try hard
104 > * to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
105 > */
106 > export interface WillShutdownEvent {
107 >
108 > /**
109 > * The reason why the application is shutting down.
110 > */
111 > readonly reason: ShutdownReason;
112 >
113 > /**
114 > * A token that will signal cancellation when the
115 > * shutdown was forced by the user.
116 > */
117 > readonly token: CancellationToken;
118 >
119 > /**
120 > * Allows to join the shutdown. The promise can be a long running operation but it
121 > * will block the application from closing.
122 > *
123 > * @param promise the promise to join the shutdown event.
124 > * @param joiner to identify the join operation in case it takes very long or never
125 > * completes.
126 > */
127 > join(promise: Promise<void>, joiner: IWillShutdownEventDefaultJoiner): void;
128 >
129 > /**
130 > * Allows to join the shutdown at the end. The promise can be a long running operation but it
131 > * will block the application from closing.
132 > *
133 > * @param promiseFn the promise to join the shutdown event.
134 > * @param joiner to identify the join operation in case it takes very long or never
135 > * completes.
136 > */
137 > join(promiseFn: (() => Promise<void>), joiner: IWillShutdownEventLastJoiner): void;
138 >
139 > /**
140 > * Allows to access the joiners that have not finished joining this event.
141 > */
142 > joiners(): IWillShutdownEventJoiner[];
143 >
144 > /**
145 > * Allows to enforce the shutdown, even when there are
146 > * pending `join` operations to complete.
147 > */
148 > force(): void;
149 > }
150 >
151 > export const enum ShutdownReason {
152 >
153 > /**
154 > * The window is closed.
155 > */
156 > CLOSE = 1,
157 >
158 > /**
159 > * The window closes because the application quits.
160 > */
161 > QUIT,
162 >
163 > /**
164 > * The window is reloaded.
165 > */
166 > RELOAD,
167 >
168 > /**
169 > * The window is loaded into a different workspace context.
170 > */
171 > LOAD
172 > }
173 >
174 > export const enum StartupKind {
175 > NewWindow = 1,
176 > ReloadedWindow = 3,
177 > ReopenedWindow = 4
178 > }
179 >
180 > export function StartupKindToString(startupKind: StartupKind): string {
181 switch (startupKind) {
182 case StartupKind.NewWindow: return 'NewWindow';
185 }
186 }
187 > lifecycle.ts
188 > export const enum LifecyclePhase {
189 >
190 > /**
191 > * The first phase signals that we are about to startup getting ready.
192 > *
193 > * Note: doing work in this phase blocks an editor from showing to
194 > * the user, so please rather consider to use `Restored` phase.
195 > */
196 > Starting = 1,
197 >
198 > /**
199 > * Services are ready and the window is about to restore its UI state.
200 > *
201 > * Note: doing work in this phase blocks an editor from showing to
202 > * the user, so please rather consider to use `Restored` phase.
203 > */
204 > Ready = 2,
205 >
206 > /**
207 > * Views, panels and editors have restored. Editors are given a bit of
208 > * time to restore their contents.
209 > */
210 > Restored = 3,
211 >
212 > /**
213 > * The last phase after views, panels and editors have restored and
214 > * some time has passed (2-5 seconds).
215 > */
216 > Eventually = 4
217 > }
218 >
219 > export function LifecyclePhaseToString(phase: LifecyclePhase): string {
220 switch (phase) {
221 case LifecyclePhase.Starting: return 'Starting';
225 }
226 }
227 > lifecycle.ts
228 > /**
229 > * A lifecycle service informs about lifecycle events of the
230 > * application, such as shutdown.
231 > */
232 > export interface ILifecycleService {
233 >
234 > readonly _serviceBrand: undefined;
235 >
236 > /**
237 > * Value indicates how this window got loaded.
238 > */
239 > readonly startupKind: StartupKind;
240 >
241 > /**
242 > * A flag indicating in what phase of the lifecycle we currently are.
243 > */
244 > phase: LifecyclePhase;
245 >
246 > /**
247 > * Fired before shutdown happens. Allows listeners to veto against the
248 > * shutdown to prevent it from happening.
249 > *
250 > * The event carries a shutdown reason that indicates how the shutdown was triggered.
251 > */
252 > readonly onBeforeShutdown: Event<BeforeShutdownEvent>;
253 >
254 > /**
255 > * Fired when the shutdown was prevented by a component giving veto.
256 > */
257 > readonly onShutdownVeto: Event<void>;
258 >
259 > /**
260 > * Fired when an error happened during `onBeforeShutdown` veto handling.
261 > * In this case the shutdown operation will not proceed because this is
262 > * an unexpected condition that is treated like a veto.
263 > *
264 > * The event carries a shutdown reason that indicates how the shutdown was triggered.
265 > */
266 > readonly onBeforeShutdownError: Event<BeforeShutdownErrorEvent>;
267 >
268 > /**
269 > * Fired when no client is preventing the shutdown from happening (from `onBeforeShutdown`).
270 > *
271 > * This event can be joined with a long running operation via `WillShutdownEvent#join()` to
272 > * handle long running shutdown operations.
273 > *
274 > * The event carries a shutdown reason that indicates how the shutdown was triggered.
275 > */
276 > readonly onWillShutdown: Event<WillShutdownEvent>;
277 >
278 > /**
279 > * A flag indicating that we are about to shutdown without further veto.
280 > */
281 > readonly willShutdown: boolean;
282 >
283 > /**
284 > * Fired when the shutdown is about to happen after long running shutdown operations
285 > * have finished (from `onWillShutdown`).
286 > *
287 > * This event should be used to dispose resources.
288 > */
289 > readonly onDidShutdown: Event<void>;
290 >
291 > /**
292 > * Returns a promise that resolves when a certain lifecycle phase
293 > * has started.
294 > */
295 > when(phase: LifecyclePhase): Promise<void>;
296 >
297 > /**
298 > * Triggers a shutdown of the workbench. Depending on native or web, this can have
299 > * different implementations and behaviour.
300 > *
301 > * **Note:** this should normally not be called. See related methods in `IHostService`
302 > * and `INativeHostService` to close a window or quit the application.
303 > */
304 > shutdown(): Promise<void>;
305 > }
src/vs/platform/defaultAccount/common/defaultAccount.ts 88 introduced LOC · 1 range

Open complete file

1 > /*--------------------------------------------------------------------------------------------- defaultAccount.ts
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 >
6 > import { ICopilotTokenInfo, IDefaultAccount, IDefaultAccountAuthenticationProvider, IPolicyData } from '../../../base/common/defaultAccount.js';
7 > import { Event } from '../../../base/common/event.js';
8 > import { createDecorator } from '../../instantiation/common/instantiation.js';
9 >
10 > /**
11 > * Well-known GitHub URL paths used with {@link IDefaultAccountService.resolveGitHubUrl}.
12 > */
13 > export const GitHubPaths = {
14 > copilotSettings: 'settings/copilot/features',
15 > billingBudgets: 'settings/copilot/features?utm_source=vscode',
16 > copilotUpgrade: 'github-copilot/upgrade?utm_source=vscode',
17 > } as const;
18 >
19 > /**
20 > * Outcome of the last `/copilot_internal/managed_settings` fetch.
21 > * - A numeric HTTP status code indicates the server responded with that code.
22 > * - `'ok'`: response parsed and adapted successfully (including an empty `{}` body).
23 > * - `'no-url'`: no `managedSettingsUrl` configured in product.json.
24 > * - `'no-response'`: network error, all sessions rejected, or active rate-limit backoff.
25 > * - `'parse-error'`: response received but JSON parsing failed.
26 > * - `null`: never fetched.
27 > */
28 > export type ManagedSettingsFetchStatus = number | 'ok' | 'no-url' | 'no-response' | 'parse-error' | null;
29 >
30 > export interface IDefaultAccountProvider {
31 > readonly defaultAccount: IDefaultAccount | null;
32 > readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null>;
33 > readonly policyData: IPolicyData | null;
34 > readonly onDidChangePolicyData: Event<IPolicyData | null>;
35 > readonly copilotTokenInfo: ICopilotTokenInfo | null;
36 > readonly onDidChangeCopilotTokenInfo: Event<ICopilotTokenInfo | null>;
37 > readonly managedSettingsFetchStatus: ManagedSettingsFetchStatus;
38 > /** Timestamp (ms) of the last managed-settings fetch, or `null` if never fetched. */
39 > readonly managedSettingsFetchedAt: number | null;
40 > /** The raw JSON response from the managed-settings endpoint, for diagnostics. */
41 > readonly managedSettingsRawResponse: unknown;
42 > getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider;
43 >
44 > /**
45 > * Resolves a GitHub URL path to a full URL, using the GitHub Enterprise
46 > * base URL when the user is authenticated via a GHE provider, or
47 > * `https://github.com` otherwise.
48 > *
49 > * @param path The path portion of the URL (e.g. `settings/copilot/features`).
50 > */
51 > resolveGitHubUrl(path: string): string;
52 >
53 > refresh(options?: { forceRefresh?: boolean }): Promise<IDefaultAccount | null>;
54 > signIn(options?: { additionalScopes?: readonly string[];[key: string]: unknown }): Promise<IDefaultAccount | null>;
55 > signOut(): Promise<void>;
56 > }
57 >
58 > export const IDefaultAccountService = createDecorator<IDefaultAccountService>('defaultAccountService');
59 >
60 > export interface IDefaultAccountService {
61 > readonly _serviceBrand: undefined;
62 > readonly onDidChangeDefaultAccount: Event<IDefaultAccount | null>;
63 > readonly onDidChangePolicyData: Event<IPolicyData | null>;
64 > readonly policyData: IPolicyData | null;
65 > readonly currentDefaultAccount: IDefaultAccount | null;
66 > readonly copilotTokenInfo: ICopilotTokenInfo | null;
67 > readonly onDidChangeCopilotTokenInfo: Event<ICopilotTokenInfo | null>;
68 > readonly managedSettingsFetchStatus: ManagedSettingsFetchStatus;
69 > /** Timestamp (ms) of the last managed-settings fetch, or `null` if never fetched. */
70 > readonly managedSettingsFetchedAt: number | null;
71 > /** The raw JSON response from the managed-settings endpoint, for diagnostics. */
72 > readonly managedSettingsRawResponse: unknown;
73 > getDefaultAccount(): Promise<IDefaultAccount | null>;
74 > getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider;
75 > setDefaultAccountProvider(provider: IDefaultAccountProvider): void;
76 > refresh(options?: { forceRefresh?: boolean }): Promise<IDefaultAccount | null>;
77 > signIn(options?: { additionalScopes?: readonly string[];[key: string]: unknown }): Promise<IDefaultAccount | null>;
78 > signOut(): Promise<void>;
79 >
80 > /**
81 > * Resolves a GitHub URL path to a full URL, using the GitHub Enterprise
82 > * base URL when the user is authenticated via a GHE provider, or
83 > * `https://github.com` otherwise.
84 > *
85 > * @param path The path portion of the URL (e.g. `settings/copilot/features`).
86 > */
87 > resolveGitHubUrl(path: string): string;
88 > }