Path: blob/main/src/vs/workbench/services/accounts/browser/defaultAccount.ts
5241 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*--------------------------------------------------------------------------------------------*/45import { Emitter } from '../../../../base/common/event.js';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { IProductService } from '../../../../platform/product/common/productService.js';8import { AuthenticationSession, AuthenticationSessionAccount, IAuthenticationExtensionsService, IAuthenticationService } from '../../authentication/common/authentication.js';9import { asJson, IRequestService } from '../../../../platform/request/common/request.js';10import { CancellationToken } from '../../../../base/common/cancellation.js';11import { IExtensionService } from '../../extensions/common/extensions.js';12import { ILogService } from '../../../../platform/log/common/log.js';13import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';14import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';15import { Barrier, RunOnceScheduler, ThrottledDelayer, timeout } from '../../../../base/common/async.js';16import { IHostService } from '../../host/browser/host.js';17import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';18import { getErrorMessage } from '../../../../base/common/errors.js';19import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IEntitlementsData, IPolicyData } from '../../../../base/common/defaultAccount.js';20import { isString, Mutable } from '../../../../base/common/types.js';21import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';22import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';23import { isWeb } from '../../../../base/common/platform.js';24import { IDefaultAccountProvider, IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js';25import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';26import { distinct } from '../../../../base/common/arrays.js';27import { equals } from '../../../../base/common/objects.js';28import { IDefaultChatAgent } from '../../../../base/common/product.js';29import { IRequestContext } from '../../../../base/parts/request/common/request.js';30import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';3132interface IDefaultAccountConfig {33readonly preferredExtensions: string[];34readonly authenticationProvider: {35readonly default: {36readonly id: string;37readonly name: string;38};39readonly enterprise: {40readonly id: string;41readonly name: string;42};43readonly enterpriseProviderConfig: string;44readonly enterpriseProviderUriSetting: string;45readonly scopes: string[][];46};47readonly tokenEntitlementUrl: string;48readonly entitlementUrl: string;49readonly mcpRegistryDataUrl: string;50}5152export const DEFAULT_ACCOUNT_SIGN_IN_COMMAND = 'workbench.actions.accounts.signIn';5354const enum DefaultAccountStatus {55Uninitialized = 'uninitialized',56Unavailable = 'unavailable',57Available = 'available',58}5960const CONTEXT_DEFAULT_ACCOUNT_STATE = new RawContextKey<string>('defaultAccountStatus', DefaultAccountStatus.Uninitialized);61const CACHED_POLICY_DATA_KEY = 'defaultAccount.cachedPolicyData';62const ACCOUNT_DATA_POLL_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes6364interface ITokenEntitlementsResponse {65token: string;66}6768interface IMcpRegistryProvider {69readonly url: string;70readonly registry_access: 'allow_all' | 'registry_only';71readonly owner: {72readonly login: string;73readonly id: number;74readonly type: string;75readonly parent_login: string | null;76readonly priority: number;77};78}7980interface IMcpRegistryResponse {81readonly mcp_registries: ReadonlyArray<IMcpRegistryProvider>;82}8384function toDefaultAccountConfig(defaultChatAgent: IDefaultChatAgent): IDefaultAccountConfig {85return {86preferredExtensions: [87defaultChatAgent.chatExtensionId,88defaultChatAgent.extensionId,89],90authenticationProvider: {91default: {92id: defaultChatAgent.provider.default.id,93name: defaultChatAgent.provider.default.name,94},95enterprise: {96id: defaultChatAgent.provider.enterprise.id,97name: defaultChatAgent.provider.enterprise.name,98},99enterpriseProviderConfig: `${defaultChatAgent.completionsAdvancedSetting}.authProvider`,100enterpriseProviderUriSetting: defaultChatAgent.providerUriSetting,101scopes: defaultChatAgent.providerScopes,102},103entitlementUrl: defaultChatAgent.entitlementUrl,104tokenEntitlementUrl: defaultChatAgent.tokenEntitlementUrl,105mcpRegistryDataUrl: defaultChatAgent.mcpRegistryDataUrl,106};107}108109export class DefaultAccountService extends Disposable implements IDefaultAccountService {110declare _serviceBrand: undefined;111112private defaultAccount: IDefaultAccount | null = null;113get policyData(): IPolicyData | null { return this.defaultAccountProvider?.policyData ?? null; }114115private readonly initBarrier = new Barrier();116117private readonly _onDidChangeDefaultAccount = this._register(new Emitter<IDefaultAccount | null>());118readonly onDidChangeDefaultAccount = this._onDidChangeDefaultAccount.event;119120private readonly _onDidChangePolicyData = this._register(new Emitter<IPolicyData | null>());121readonly onDidChangePolicyData = this._onDidChangePolicyData.event;122123private readonly defaultAccountConfig: IDefaultAccountConfig;124private defaultAccountProvider: IDefaultAccountProvider | null = null;125126constructor(127@IProductService productService: IProductService,128) {129super();130this.defaultAccountConfig = toDefaultAccountConfig(productService.defaultChatAgent);131}132133async getDefaultAccount(): Promise<IDefaultAccount | null> {134await this.initBarrier.wait();135return this.defaultAccount;136}137138getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider {139if (this.defaultAccountProvider) {140return this.defaultAccountProvider.getDefaultAccountAuthenticationProvider();141}142return {143...this.defaultAccountConfig.authenticationProvider.default,144enterprise: false145};146}147148setDefaultAccountProvider(provider: IDefaultAccountProvider): void {149if (this.defaultAccountProvider) {150throw new Error('Default account provider is already set');151}152153this.defaultAccountProvider = provider;154if (this.defaultAccountProvider.policyData) {155this._onDidChangePolicyData.fire(this.defaultAccountProvider.policyData);156}157provider.refresh().then(account => {158this.defaultAccount = account;159}).finally(() => {160this.initBarrier.open();161this._register(provider.onDidChangeDefaultAccount(account => this.setDefaultAccount(account)));162this._register(provider.onDidChangePolicyData(policyData => this._onDidChangePolicyData.fire(policyData)));163});164}165166async refresh(): Promise<IDefaultAccount | null> {167await this.initBarrier.wait();168169const account = await this.defaultAccountProvider?.refresh();170this.setDefaultAccount(account ?? null);171return this.defaultAccount;172}173174async signIn(options?: { additionalScopes?: readonly string[];[key: string]: unknown }): Promise<IDefaultAccount | null> {175await this.initBarrier.wait();176return this.defaultAccountProvider?.signIn(options) ?? null;177}178179private setDefaultAccount(account: IDefaultAccount | null): void {180if (equals(this.defaultAccount, account)) {181return;182}183this.defaultAccount = account;184this._onDidChangeDefaultAccount.fire(this.defaultAccount);185}186}187188interface IAccountPolicyData {189readonly accountId: string;190readonly policyData: IPolicyData;191}192193interface IDefaultAccountData {194defaultAccount: IDefaultAccount;195policyData: IAccountPolicyData | null;196}197198type DefaultAccountStatusTelemetry = {199status: string;200initial: boolean;201};202203type DefaultAccountStatusTelemetryClassification = {204owner: 'sandy081';205comment: 'Log default account availability status';206status: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Indicates whether default account is available or not.' };207initial: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Indicates whether this is the initial status report.' };208};209210class DefaultAccountProvider extends Disposable implements IDefaultAccountProvider {211212private _defaultAccount: IDefaultAccountData | null = null;213get defaultAccount(): IDefaultAccount | null { return this._defaultAccount?.defaultAccount ?? null; }214215private _policyData: IAccountPolicyData | null = null;216get policyData(): IPolicyData | null { return this._policyData?.policyData ?? null; }217218private readonly _onDidChangeDefaultAccount = this._register(new Emitter<IDefaultAccount | null>());219readonly onDidChangeDefaultAccount = this._onDidChangeDefaultAccount.event;220221private readonly _onDidChangePolicyData = this._register(new Emitter<IPolicyData | null>());222readonly onDidChangePolicyData = this._onDidChangePolicyData.event;223224private readonly accountStatusContext: IContextKey<string>;225private initialized = false;226private readonly initPromise: Promise<void>;227private readonly updateThrottler = this._register(new ThrottledDelayer(100));228private readonly accountDataPollScheduler = this._register(new RunOnceScheduler(() => this.updateDefaultAccount(), ACCOUNT_DATA_POLL_INTERVAL_MS));229230constructor(231private readonly defaultAccountConfig: IDefaultAccountConfig,232@IConfigurationService private readonly configurationService: IConfigurationService,233@IAuthenticationService private readonly authenticationService: IAuthenticationService,234@IAuthenticationExtensionsService private readonly authenticationExtensionsService: IAuthenticationExtensionsService,235@ITelemetryService private readonly telemetryService: ITelemetryService,236@IExtensionService private readonly extensionService: IExtensionService,237@IRequestService private readonly requestService: IRequestService,238@ILogService private readonly logService: ILogService,239@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,240@IContextKeyService contextKeyService: IContextKeyService,241@IStorageService private readonly storageService: IStorageService,242@IHostService private readonly hostService: IHostService,243) {244super();245this.accountStatusContext = CONTEXT_DEFAULT_ACCOUNT_STATE.bindTo(contextKeyService);246this._policyData = this.getCachedPolicyData();247this.initPromise = this.init()248.finally(() => {249this.telemetryService.publicLog2<DefaultAccountStatusTelemetry, DefaultAccountStatusTelemetryClassification>('defaultaccount:status', { status: this.defaultAccount ? 'available' : 'unavailable', initial: true });250this.initialized = true;251});252}253254private getCachedPolicyData(): IAccountPolicyData | null {255const cached = this.storageService.get(CACHED_POLICY_DATA_KEY, StorageScope.APPLICATION);256if (cached) {257try {258const { accountId, policyData } = JSON.parse(cached);259if (accountId && policyData) {260this.logService.debug('[DefaultAccount] Initializing with cached policy data');261return { accountId, policyData };262}263} catch (error) {264this.logService.error('[DefaultAccount] Failed to parse cached policy data', getErrorMessage(error));265}266}267return null;268}269270private async init(): Promise<void> {271if (isWeb && !this.environmentService.remoteAuthority) {272this.logService.debug('[DefaultAccount] Running in web without remote, skipping initialization');273return;274}275276try {277await this.extensionService.whenInstalledExtensionsRegistered();278this.logService.debug('[DefaultAccount] Installed extensions registered.');279} catch (error) {280this.logService.error('[DefaultAccount] Error while waiting for installed extensions to be registered', getErrorMessage(error));281}282283this.logService.debug('[DefaultAccount] Starting initialization');284await this.doUpdateDefaultAccount();285this.logService.debug('[DefaultAccount] Initialization complete');286287this._register(this.onDidChangeDefaultAccount(account => {288this.telemetryService.publicLog2<DefaultAccountStatusTelemetry, DefaultAccountStatusTelemetryClassification>('defaultaccount:status', { status: account ? 'available' : 'unavailable', initial: false });289}));290291this._register(this.authenticationService.onDidChangeSessions(e => {292const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();293if (e.providerId !== defaultAccountProvider.id) {294return;295}296if (this.defaultAccount && e.event.removed?.some(session => session.id === this.defaultAccount?.sessionId)) {297this.setDefaultAccount(null);298} else {299this.logService.debug('[DefaultAccount] Sessions changed for default account provider, updating default account');300this.updateDefaultAccount();301}302}));303304this._register(this.authenticationExtensionsService.onDidChangeAccountPreference(async e => {305const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();306if (e.providerId !== defaultAccountProvider.id) {307return;308}309this.logService.debug('[DefaultAccount] Account preference changed for default account provider, updating default account');310this.updateDefaultAccount();311}));312313this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => {314const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();315if (e.id !== defaultAccountProvider.id) {316return;317}318this.logService.debug('[DefaultAccount] Default account provider registered, updating default account');319this.updateDefaultAccount();320}));321322this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => {323const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();324if (e.id !== defaultAccountProvider.id) {325return;326}327this.logService.debug('[DefaultAccount] Default account provider unregistered, updating default account');328this.updateDefaultAccount();329}));330331this._register(this.hostService.onDidChangeFocus(focused => {332if (focused && this._defaultAccount) {333// Update default account when window gets focused334this.accountDataPollScheduler.cancel();335this.logService.debug('[DefaultAccount] Window focused, updating default account');336this.updateDefaultAccount();337}338}));339}340341async refresh(): Promise<IDefaultAccount | null> {342if (!this.initialized) {343await this.initPromise;344return this.defaultAccount;345}346347this.logService.debug('[DefaultAccount] Refreshing default account');348await this.updateDefaultAccount();349return this.defaultAccount;350}351352private async updateDefaultAccount(): Promise<void> {353await this.updateThrottler.trigger(() => this.doUpdateDefaultAccount());354}355356private async doUpdateDefaultAccount(): Promise<void> {357try {358const defaultAccount = await this.fetchDefaultAccount();359this.setDefaultAccount(defaultAccount);360this.scheduleAccountDataPoll();361} catch (error) {362this.logService.error('[DefaultAccount] Error while updating default account', getErrorMessage(error));363}364}365366private async fetchDefaultAccount(): Promise<IDefaultAccountData | null> {367const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();368this.logService.debug('[DefaultAccount] Default account provider ID:', defaultAccountProvider.id);369370const declaredProvider = this.authenticationService.declaredProviders.find(provider => provider.id === defaultAccountProvider.id);371if (!declaredProvider) {372this.logService.info(`[DefaultAccount] Authentication provider is not declared.`, defaultAccountProvider);373return null;374}375376return await this.getDefaultAccountForAuthenticationProvider(defaultAccountProvider);377}378379private setDefaultAccount(account: IDefaultAccountData | null): void {380if (equals(this._defaultAccount, account)) {381return;382}383384this.logService.trace('[DefaultAccount] Updating default account:', account);385if (account) {386this._defaultAccount = account;387this.setPolicyData(account.policyData);388this._onDidChangeDefaultAccount.fire(this._defaultAccount.defaultAccount);389this.accountStatusContext.set(DefaultAccountStatus.Available);390this.logService.debug('[DefaultAccount] Account status set to Available');391} else {392this._defaultAccount = null;393this.setPolicyData(null);394this._onDidChangeDefaultAccount.fire(null);395this.accountDataPollScheduler.cancel();396this.accountStatusContext.set(DefaultAccountStatus.Unavailable);397this.logService.debug('[DefaultAccount] Account status set to Unavailable');398}399}400401private setPolicyData(accountPolicyData: IAccountPolicyData | null): void {402if (equals(this._policyData, accountPolicyData)) {403return;404}405this._policyData = accountPolicyData;406this.cachePolicyData(accountPolicyData);407this._onDidChangePolicyData.fire(this._policyData?.policyData ?? null);408}409410private cachePolicyData(accountPolicyData: IAccountPolicyData | null): void {411if (accountPolicyData) {412this.logService.debug('[DefaultAccount] Caching policy data for account:', accountPolicyData.accountId);413this.storageService.store(CACHED_POLICY_DATA_KEY, JSON.stringify(accountPolicyData), StorageScope.APPLICATION, StorageTarget.MACHINE);414} else {415this.logService.debug('[DefaultAccount] Removing cached policy data');416this.storageService.remove(CACHED_POLICY_DATA_KEY, StorageScope.APPLICATION);417}418}419420private scheduleAccountDataPoll(): void {421if (!this._defaultAccount) {422return;423}424this.accountDataPollScheduler.schedule(ACCOUNT_DATA_POLL_INTERVAL_MS);425}426427private extractFromToken(token: string): Map<string, string> {428const result = new Map<string, string>();429const firstPart = token?.split(':')[0];430const fields = firstPart?.split(';');431for (const field of fields) {432const [key, value] = field.split('=');433result.set(key, value);434}435this.logService.debug(`[DefaultAccount] extractFromToken: ${JSON.stringify(Object.fromEntries(result))}`);436return result;437}438439private async getDefaultAccountForAuthenticationProvider(authenticationProvider: IDefaultAccountAuthenticationProvider): Promise<IDefaultAccountData | null> {440try {441this.logService.debug('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authenticationProvider.id);442const sessions = await this.findMatchingProviderSession(authenticationProvider.id, this.defaultAccountConfig.authenticationProvider.scopes);443444if (!sessions?.length) {445this.logService.debug('[DefaultAccount] No matching session found for provider:', authenticationProvider.id);446return null;447}448449return this.getDefaultAccountFromAuthenticatedSessions(authenticationProvider, sessions);450} catch (error) {451this.logService.error('[DefaultAccount] Failed to get default account for provider:', authenticationProvider.id, getErrorMessage(error));452return null;453}454}455456private async getDefaultAccountFromAuthenticatedSessions(authenticationProvider: IDefaultAccountAuthenticationProvider, sessions: AuthenticationSession[]): Promise<IDefaultAccountData | null> {457try {458const accountId = sessions[0].account.id;459const [entitlementsData, tokenEntitlementsData] = await Promise.all([460this.getEntitlements(sessions),461this.getTokenEntitlements(sessions),462]);463464let policyData: Mutable<IPolicyData> | undefined = this._policyData?.accountId === accountId ? { ...this._policyData.policyData } : undefined;465if (tokenEntitlementsData) {466policyData = policyData ?? {};467policyData.chat_agent_enabled = tokenEntitlementsData.chat_agent_enabled;468policyData.chat_preview_features_enabled = tokenEntitlementsData.chat_preview_features_enabled;469policyData.mcp = tokenEntitlementsData.mcp;470if (policyData.mcp) {471const mcpRegistryProvider = await this.getMcpRegistryProvider(sessions);472if (mcpRegistryProvider) {473policyData.mcpRegistryUrl = mcpRegistryProvider.url;474policyData.mcpAccess = mcpRegistryProvider.registry_access;475}476}477}478479const defaultAccount: IDefaultAccount = {480authenticationProvider,481sessionId: sessions[0].id,482enterprise: authenticationProvider.enterprise || sessions[0].account.label.includes('_'),483entitlementsData,484};485this.logService.debug('[DefaultAccount] Successfully created default account for provider:', authenticationProvider.id);486return { defaultAccount, policyData: policyData ? { accountId, policyData } : null };487} catch (error) {488this.logService.error('[DefaultAccount] Failed to create default account for provider:', authenticationProvider.id, getErrorMessage(error));489return null;490}491}492493private async findMatchingProviderSession(authProviderId: string, allScopes: string[][]): Promise<AuthenticationSession[] | undefined> {494const sessions = await this.getSessions(authProviderId);495const matchingSessions = [];496for (const session of sessions) {497this.logService.debug('[DefaultAccount] Checking session with scopes', session.scopes);498for (const scopes of allScopes) {499if (this.scopesMatch(session.scopes, scopes)) {500matchingSessions.push(session);501}502}503}504return matchingSessions.length > 0 ? matchingSessions : undefined;505}506507private async getSessions(authProviderId: string): Promise<readonly AuthenticationSession[]> {508for (let attempt = 1; attempt <= 3; attempt++) {509try {510let preferredAccount: AuthenticationSessionAccount | undefined;511let preferredAccountName: string | undefined;512for (const preferredExtension of this.defaultAccountConfig.preferredExtensions) {513preferredAccountName = this.authenticationExtensionsService.getAccountPreference(preferredExtension, authProviderId);514if (preferredAccountName) {515break;516}517}518for (const account of await this.authenticationService.getAccounts(authProviderId)) {519if (account.label === preferredAccountName) {520preferredAccount = account;521break;522}523}524525return await this.authenticationService.getSessions(authProviderId, undefined, { account: preferredAccount }, true);526} catch (error) {527this.logService.warn(`[DefaultAccount] Attempt ${attempt} to get sessions failed:`, getErrorMessage(error));528if (attempt === 3) {529throw error;530}531await timeout(500);532}533}534throw new Error('Unable to get sessions after multiple attempts');535}536537private scopesMatch(scopes: ReadonlyArray<string>, expectedScopes: string[]): boolean {538return expectedScopes.every(scope => scopes.includes(scope));539}540541private async getTokenEntitlements(sessions: AuthenticationSession[]): Promise<Partial<IPolicyData> | undefined> {542const tokenEntitlementsUrl = this.getTokenEntitlementUrl();543if (!tokenEntitlementsUrl) {544this.logService.debug('[DefaultAccount] No token entitlements URL found');545return undefined;546}547548this.logService.debug('[DefaultAccount] Fetching token entitlements from:', tokenEntitlementsUrl);549const response = await this.request(tokenEntitlementsUrl, 'GET', undefined, sessions, CancellationToken.None);550if (!response) {551return undefined;552}553554if (response.res.statusCode && response.res.statusCode !== 200) {555this.logService.trace(`[DefaultAccount] unexpected status code ${response.res.statusCode} while fetching token entitlements`);556return undefined;557}558559try {560const chatData = await asJson<ITokenEntitlementsResponse>(response);561if (chatData) {562const tokenMap = this.extractFromToken(chatData.token);563return {564// Editor preview features are disabled if the flag is present and set to 0565chat_preview_features_enabled: tokenMap.get('editor_preview_features') !== '0',566chat_agent_enabled: tokenMap.get('agent_mode') !== '0',567// MCP is disabled if the flag is present and set to 0568mcp: tokenMap.get('mcp') !== '0',569};570}571this.logService.error('Failed to fetch token entitlements', 'No data returned');572} catch (error) {573this.logService.error('Failed to fetch token entitlements', getErrorMessage(error));574}575576return undefined;577}578579private async getEntitlements(sessions: AuthenticationSession[]): Promise<IEntitlementsData | undefined | null> {580const entitlementUrl = this.getEntitlementUrl();581if (!entitlementUrl) {582this.logService.debug('[DefaultAccount] No chat entitlements URL found');583return undefined;584}585586this.logService.debug('[DefaultAccount] Fetching entitlements from:', entitlementUrl);587const response = await this.request(entitlementUrl, 'GET', undefined, sessions, CancellationToken.None);588if (!response) {589return undefined;590}591592if (response.res.statusCode && response.res.statusCode !== 200) {593this.logService.trace(`[DefaultAccount] unexpected status code ${response.res.statusCode} while fetching entitlements`);594return (595response.res.statusCode === 401 || // oauth token being unavailable (expired/revoked)596response.res.statusCode === 404 // missing scopes/permissions, service pretends the endpoint doesn't exist597) ? null : undefined;598}599600try {601const data = await asJson<IEntitlementsData>(response);602if (data) {603return data;604}605this.logService.error('[DefaultAccount] Failed to fetch entitlements', 'No data returned');606} catch (error) {607this.logService.error('[DefaultAccount] Failed to fetch entitlements', getErrorMessage(error));608}609return undefined;610}611612private async getMcpRegistryProvider(sessions: AuthenticationSession[]): Promise<IMcpRegistryProvider | undefined> {613const mcpRegistryDataUrl = this.getMcpRegistryDataUrl();614if (!mcpRegistryDataUrl) {615this.logService.debug('[DefaultAccount] No MCP registry data URL found');616return undefined;617}618619this.logService.debug('[DefaultAccount] Fetching MCP registry data from:', mcpRegistryDataUrl);620const response = await this.request(mcpRegistryDataUrl, 'GET', undefined, sessions, CancellationToken.None);621if (!response) {622return undefined;623}624625if (response.res.statusCode && response.res.statusCode !== 200) {626this.logService.trace(`[DefaultAccount] unexpected status code ${response.res.statusCode} while fetching MCP registry data`);627return undefined;628}629630try {631const data = await asJson<IMcpRegistryResponse>(response);632if (data) {633this.logService.debug('Fetched MCP registry providers', data.mcp_registries);634return data.mcp_registries[0];635}636this.logService.debug('Failed to fetch MCP registry providers', 'No data returned');637} catch (error) {638this.logService.error('Failed to fetch MCP registry providers', getErrorMessage(error));639}640return undefined;641}642643private async request(url: string, type: 'GET', body: undefined, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined>;644private async request(url: string, type: 'POST', body: object, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined>;645private async request(url: string, type: 'GET' | 'POST', body: object | undefined, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined> {646let lastResponse: IRequestContext | undefined;647648for (const session of sessions) {649if (token.isCancellationRequested) {650return lastResponse;651}652653try {654const response = await this.requestService.request({655type,656url,657data: type === 'POST' ? JSON.stringify(body) : undefined,658disableCache: true,659headers: {660'Authorization': `Bearer ${session.accessToken}`661}662}, token);663664const status = response.res.statusCode;665if (status && status !== 200) {666lastResponse = response;667continue; // try next session668}669670return response;671} catch (error) {672if (!token.isCancellationRequested) {673this.logService.error(`[chat entitlement] request: error ${error}`);674}675}676}677678if (!lastResponse) {679this.logService.trace('[DefaultAccount]: No response received for request', url);680return undefined;681}682683if (lastResponse.res.statusCode && lastResponse.res.statusCode !== 200) {684this.logService.trace(`[DefaultAccount]: unexpected status code ${lastResponse.res.statusCode} for request`, url);685return undefined;686}687688return lastResponse;689}690691private getEntitlementUrl(): string | undefined {692if (this.getDefaultAccountAuthenticationProvider().enterprise) {693try {694const enterpriseUrl = this.getEnterpriseUrl();695if (!enterpriseUrl) {696return undefined;697}698return `${enterpriseUrl.protocol}//api.${enterpriseUrl.hostname}${enterpriseUrl.port ? ':' + enterpriseUrl.port : ''}/copilot_internal/user`;699} catch (error) {700this.logService.error(error);701}702}703704return this.defaultAccountConfig.entitlementUrl;705}706707private getTokenEntitlementUrl(): string | undefined {708if (this.getDefaultAccountAuthenticationProvider().enterprise) {709try {710const enterpriseUrl = this.getEnterpriseUrl();711if (!enterpriseUrl) {712return undefined;713}714return `${enterpriseUrl.protocol}//api.${enterpriseUrl.hostname}${enterpriseUrl.port ? ':' + enterpriseUrl.port : ''}/copilot_internal/v2/token`;715} catch (error) {716this.logService.error(error);717}718}719720return this.defaultAccountConfig.tokenEntitlementUrl;721}722723private getMcpRegistryDataUrl(): string | undefined {724if (this.getDefaultAccountAuthenticationProvider().enterprise) {725try {726const enterpriseUrl = this.getEnterpriseUrl();727if (!enterpriseUrl) {728return undefined;729}730return `${enterpriseUrl.protocol}//api.${enterpriseUrl.hostname}${enterpriseUrl.port ? ':' + enterpriseUrl.port : ''}/copilot/mcp_registry`;731} catch (error) {732this.logService.error(error);733}734}735736return this.defaultAccountConfig.mcpRegistryDataUrl;737}738739getDefaultAccountAuthenticationProvider(): IDefaultAccountAuthenticationProvider {740if (this.configurationService.getValue<string | undefined>(this.defaultAccountConfig.authenticationProvider.enterpriseProviderConfig) === this.defaultAccountConfig.authenticationProvider.enterprise.id) {741return {742...this.defaultAccountConfig.authenticationProvider.enterprise,743enterprise: true744};745}746return {747...this.defaultAccountConfig.authenticationProvider.default,748enterprise: false749};750}751752private getEnterpriseUrl(): URL | undefined {753const value = this.configurationService.getValue(this.defaultAccountConfig.authenticationProvider.enterpriseProviderUriSetting);754if (!isString(value)) {755return undefined;756}757return new URL(value);758}759760async signIn(options?: { additionalScopes?: readonly string[];[key: string]: unknown }): Promise<IDefaultAccount | null> {761const authProvider = this.getDefaultAccountAuthenticationProvider();762if (!authProvider) {763throw new Error('No default account provider configured');764}765const { additionalScopes, ...sessionOptions } = options ?? {};766const defaultAccountScopes = this.defaultAccountConfig.authenticationProvider.scopes[0];767const scopes = additionalScopes ? distinct([...defaultAccountScopes, ...additionalScopes]) : defaultAccountScopes;768const session = await this.authenticationService.createSession(authProvider.id, scopes, sessionOptions);769for (const preferredExtension of this.defaultAccountConfig.preferredExtensions) {770this.authenticationExtensionsService.updateAccountPreference(preferredExtension, authProvider.id, session.account);771}772await this.updateDefaultAccount();773return this.defaultAccount;774}775776}777778class DefaultAccountProviderContribution extends Disposable implements IWorkbenchContribution {779780static ID = 'workbench.contributions.defaultAccountProvider';781782constructor(783@IProductService productService: IProductService,784@IInstantiationService instantiationService: IInstantiationService,785@IDefaultAccountService defaultAccountService: IDefaultAccountService,786) {787super();788const defaultAccountProvider = this._register(instantiationService.createInstance(DefaultAccountProvider, toDefaultAccountConfig(productService.defaultChatAgent)));789defaultAccountService.setDefaultAccountProvider(defaultAccountProvider);790}791}792793registerWorkbenchContribution2(DefaultAccountProviderContribution.ID, DefaultAccountProviderContribution, WorkbenchPhase.BlockStartup);794795796