mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
do not fetch policy data if already fetched (#293995)
* do not fetch policy data if already fetched * reset mcp data when not configured and refetch on polling * refetch always * reschedule when window has no focus * rename * update comment
This commit is contained in:
committed by
GitHub
parent
0911b9789f
commit
cd71c4b8af
@@ -18,7 +18,7 @@ import { IHostService } from '../../host/browser/host.js';
|
||||
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
|
||||
import { getErrorMessage } from '../../../../base/common/errors.js';
|
||||
import { IDefaultAccount, IDefaultAccountAuthenticationProvider, IEntitlementsData, IPolicyData } from '../../../../base/common/defaultAccount.js';
|
||||
import { isString, Mutable } from '../../../../base/common/types.js';
|
||||
import { isString, isUndefined, Mutable } from '../../../../base/common/types.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
|
||||
import { isWeb } from '../../../../base/common/platform.js';
|
||||
@@ -60,7 +60,7 @@ const enum DefaultAccountStatus {
|
||||
|
||||
const CONTEXT_DEFAULT_ACCOUNT_STATE = new RawContextKey<string>('defaultAccountStatus', DefaultAccountStatus.Uninitialized);
|
||||
const CACHED_POLICY_DATA_KEY = 'defaultAccount.cachedPolicyData';
|
||||
const ACCOUNT_DATA_POLL_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
|
||||
const ACCOUNT_DATA_POLL_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
interface ITokenEntitlementsResponse {
|
||||
token: string;
|
||||
@@ -69,7 +69,7 @@ interface ITokenEntitlementsResponse {
|
||||
interface IMcpRegistryProvider {
|
||||
readonly url: string;
|
||||
readonly registry_access: 'allow_all' | 'registry_only';
|
||||
readonly owner: {
|
||||
readonly owner?: {
|
||||
readonly login: string;
|
||||
readonly id: number;
|
||||
readonly type: string;
|
||||
@@ -189,6 +189,8 @@ export class DefaultAccountService extends Disposable implements IDefaultAccount
|
||||
interface IAccountPolicyData {
|
||||
readonly accountId: string;
|
||||
readonly policyData: IPolicyData;
|
||||
readonly isTokenEntitlementsDataFetched: boolean;
|
||||
readonly isMcpRegistryDataFetched: boolean;
|
||||
}
|
||||
|
||||
interface IDefaultAccountData {
|
||||
@@ -226,7 +228,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
private initialized = false;
|
||||
private readonly initPromise: Promise<void>;
|
||||
private readonly updateThrottler = this._register(new ThrottledDelayer(100));
|
||||
private readonly accountDataPollScheduler = this._register(new RunOnceScheduler(() => this.updateDefaultAccount(), ACCOUNT_DATA_POLL_INTERVAL_MS));
|
||||
private readonly accountDataPollScheduler = this._register(new RunOnceScheduler(() => this.refetchDefaultAccount(), ACCOUNT_DATA_POLL_INTERVAL_MS));
|
||||
|
||||
constructor(
|
||||
private readonly defaultAccountConfig: IDefaultAccountConfig,
|
||||
@@ -259,7 +261,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
const { accountId, policyData } = JSON.parse(cached);
|
||||
if (accountId && policyData) {
|
||||
this.logService.debug('[DefaultAccount] Initializing with cached policy data');
|
||||
return { accountId, policyData };
|
||||
return { accountId, policyData, isTokenEntitlementsDataFetched: false, isMcpRegistryDataFetched: false };
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to parse cached policy data', getErrorMessage(error));
|
||||
@@ -282,7 +284,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
}
|
||||
|
||||
this.logService.debug('[DefaultAccount] Starting initialization');
|
||||
await this.doUpdateDefaultAccount();
|
||||
await this.doUpdateDefaultAccount(false);
|
||||
this.logService.debug('[DefaultAccount] Initialization complete');
|
||||
|
||||
this._register(this.onDidChangeDefaultAccount(account => {
|
||||
@@ -330,11 +332,11 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
}));
|
||||
|
||||
this._register(this.hostService.onDidChangeFocus(focused => {
|
||||
if (focused && this._defaultAccount) {
|
||||
// Update default account when window gets focused
|
||||
// Refresh default account when window gets focused and policy data is not fully fetched, to ensure we have the latest policy data.
|
||||
if (focused && this._policyData && (!this._policyData.isMcpRegistryDataFetched || !this._policyData.isTokenEntitlementsDataFetched)) {
|
||||
this.accountDataPollScheduler.cancel();
|
||||
this.logService.debug('[DefaultAccount] Window focused, updating default account');
|
||||
this.updateDefaultAccount();
|
||||
this.refresh();
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -350,13 +352,23 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return this.defaultAccount;
|
||||
}
|
||||
|
||||
private async updateDefaultAccount(): Promise<void> {
|
||||
await this.updateThrottler.trigger(() => this.doUpdateDefaultAccount());
|
||||
private async refetchDefaultAccount(): Promise<void> {
|
||||
if (!this.hostService.hasFocus) {
|
||||
this.scheduleAccountDataPoll();
|
||||
this.logService.debug('[DefaultAccount] Skipping refetching default account because window is not focused');
|
||||
return;
|
||||
}
|
||||
this.logService.debug('[DefaultAccount] Refetching default account');
|
||||
await this.updateDefaultAccount(true);
|
||||
}
|
||||
|
||||
private async doUpdateDefaultAccount(): Promise<void> {
|
||||
private async updateDefaultAccount(donotUseLastFetchedData: boolean = false): Promise<void> {
|
||||
await this.updateThrottler.trigger(() => this.doUpdateDefaultAccount(donotUseLastFetchedData));
|
||||
}
|
||||
|
||||
private async doUpdateDefaultAccount(donotUseLastFetchedData: boolean): Promise<void> {
|
||||
try {
|
||||
const defaultAccount = await this.fetchDefaultAccount();
|
||||
const defaultAccount = await this.fetchDefaultAccount(donotUseLastFetchedData);
|
||||
this.setDefaultAccount(defaultAccount);
|
||||
this.scheduleAccountDataPoll();
|
||||
} catch (error) {
|
||||
@@ -364,7 +376,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchDefaultAccount(): Promise<IDefaultAccountData | null> {
|
||||
private async fetchDefaultAccount(donotUseLastFetchedData: boolean): Promise<IDefaultAccountData | null> {
|
||||
const defaultAccountProvider = this.getDefaultAccountAuthenticationProvider();
|
||||
this.logService.debug('[DefaultAccount] Default account provider ID:', defaultAccountProvider.id);
|
||||
|
||||
@@ -374,7 +386,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.getDefaultAccountForAuthenticationProvider(defaultAccountProvider);
|
||||
return await this.getDefaultAccountForAuthenticationProvider(defaultAccountProvider, donotUseLastFetchedData);
|
||||
}
|
||||
|
||||
private setDefaultAccount(account: IDefaultAccountData | null): void {
|
||||
@@ -437,7 +449,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getDefaultAccountForAuthenticationProvider(authenticationProvider: IDefaultAccountAuthenticationProvider): Promise<IDefaultAccountData | null> {
|
||||
private async getDefaultAccountForAuthenticationProvider(authenticationProvider: IDefaultAccountAuthenticationProvider, donotUseLastFetchedData: boolean): Promise<IDefaultAccountData | null> {
|
||||
try {
|
||||
this.logService.debug('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authenticationProvider.id);
|
||||
const sessions = await this.findMatchingProviderSession(authenticationProvider.id, this.defaultAccountConfig.authenticationProvider.scopes);
|
||||
@@ -447,33 +459,42 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getDefaultAccountFromAuthenticatedSessions(authenticationProvider, sessions);
|
||||
return this.getDefaultAccountFromAuthenticatedSessions(authenticationProvider, sessions, donotUseLastFetchedData);
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to get default account for provider:', authenticationProvider.id, getErrorMessage(error));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async getDefaultAccountFromAuthenticatedSessions(authenticationProvider: IDefaultAccountAuthenticationProvider, sessions: AuthenticationSession[]): Promise<IDefaultAccountData | null> {
|
||||
private async getDefaultAccountFromAuthenticatedSessions(authenticationProvider: IDefaultAccountAuthenticationProvider, sessions: AuthenticationSession[], donotUseLastFetchedData: boolean): Promise<IDefaultAccountData | null> {
|
||||
try {
|
||||
const accountId = sessions[0].account.id;
|
||||
const accountPolicyData = !donotUseLastFetchedData && this._policyData?.accountId === accountId ? this._policyData : undefined;
|
||||
|
||||
const [entitlementsData, tokenEntitlementsData] = await Promise.all([
|
||||
this.getEntitlements(sessions),
|
||||
this.getTokenEntitlements(sessions),
|
||||
this.getTokenEntitlements(sessions, accountPolicyData),
|
||||
]);
|
||||
|
||||
let policyData: Mutable<IPolicyData> | undefined = this._policyData?.accountId === accountId ? { ...this._policyData.policyData } : undefined;
|
||||
let isTokenEntitlementsDataFetched = false;
|
||||
let isMcpRegistryDataFetched = false;
|
||||
let policyData: Mutable<IPolicyData> | undefined = accountPolicyData?.policyData ? { ...accountPolicyData.policyData } : undefined;
|
||||
if (tokenEntitlementsData) {
|
||||
isTokenEntitlementsDataFetched = true;
|
||||
policyData = policyData ?? {};
|
||||
policyData.chat_agent_enabled = tokenEntitlementsData.chat_agent_enabled;
|
||||
policyData.chat_preview_features_enabled = tokenEntitlementsData.chat_preview_features_enabled;
|
||||
policyData.mcp = tokenEntitlementsData.mcp;
|
||||
if (policyData.mcp) {
|
||||
const mcpRegistryProvider = await this.getMcpRegistryProvider(sessions);
|
||||
if (mcpRegistryProvider) {
|
||||
policyData.mcpRegistryUrl = mcpRegistryProvider.url;
|
||||
policyData.mcpAccess = mcpRegistryProvider.registry_access;
|
||||
const mcpRegistryProvider = await this.getMcpRegistryProvider(sessions, accountPolicyData);
|
||||
if (!isUndefined(mcpRegistryProvider)) {
|
||||
isMcpRegistryDataFetched = true;
|
||||
policyData.mcpRegistryUrl = mcpRegistryProvider?.url;
|
||||
policyData.mcpAccess = mcpRegistryProvider?.registry_access;
|
||||
}
|
||||
} else {
|
||||
policyData.mcpRegistryUrl = undefined;
|
||||
policyData.mcpAccess = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +505,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
entitlementsData,
|
||||
};
|
||||
this.logService.debug('[DefaultAccount] Successfully created default account for provider:', authenticationProvider.id);
|
||||
return { defaultAccount, policyData: policyData ? { accountId, policyData } : null };
|
||||
return { defaultAccount, policyData: policyData ? { accountId, policyData, isTokenEntitlementsDataFetched, isMcpRegistryDataFetched } : null };
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to create default account for provider:', authenticationProvider.id, getErrorMessage(error));
|
||||
return null;
|
||||
@@ -539,7 +560,15 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return expectedScopes.every(scope => scopes.includes(scope));
|
||||
}
|
||||
|
||||
private async getTokenEntitlements(sessions: AuthenticationSession[]): Promise<Partial<IPolicyData> | undefined> {
|
||||
private async getTokenEntitlements(sessions: AuthenticationSession[], accountPolicyData: IAccountPolicyData | undefined): Promise<Partial<IPolicyData> | undefined> {
|
||||
if (accountPolicyData?.isTokenEntitlementsDataFetched) {
|
||||
this.logService.debug('[DefaultAccount] Using last fetched token entitlements data');
|
||||
return accountPolicyData.policyData;
|
||||
}
|
||||
return await this.requestTokenEntitlements(sessions);
|
||||
}
|
||||
|
||||
private async requestTokenEntitlements(sessions: AuthenticationSession[]): Promise<Partial<IPolicyData> | undefined> {
|
||||
const tokenEntitlementsUrl = this.getTokenEntitlementUrl();
|
||||
if (!tokenEntitlementsUrl) {
|
||||
this.logService.debug('[DefaultAccount] No token entitlements URL found');
|
||||
@@ -610,11 +639,19 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async getMcpRegistryProvider(sessions: AuthenticationSession[]): Promise<IMcpRegistryProvider | undefined> {
|
||||
private async getMcpRegistryProvider(sessions: AuthenticationSession[], accountPolicyData: IAccountPolicyData | undefined): Promise<IMcpRegistryProvider | null | undefined> {
|
||||
if (accountPolicyData?.isMcpRegistryDataFetched) {
|
||||
this.logService.debug('[DefaultAccount] Using last fetched MCP registry data');
|
||||
return accountPolicyData.policyData.mcpRegistryUrl && accountPolicyData.policyData.mcpAccess ? { url: accountPolicyData.policyData.mcpRegistryUrl, registry_access: accountPolicyData.policyData.mcpAccess } : null;
|
||||
}
|
||||
return await this.requestMcpRegistryProvider(sessions);
|
||||
}
|
||||
|
||||
private async requestMcpRegistryProvider(sessions: AuthenticationSession[]): Promise<IMcpRegistryProvider | null | undefined> {
|
||||
const mcpRegistryDataUrl = this.getMcpRegistryDataUrl();
|
||||
if (!mcpRegistryDataUrl) {
|
||||
this.logService.debug('[DefaultAccount] No MCP registry data URL found');
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.logService.debug('[DefaultAccount] Fetching MCP registry data from:', mcpRegistryDataUrl);
|
||||
@@ -625,20 +662,23 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
|
||||
if (response.res.statusCode && response.res.statusCode !== 200) {
|
||||
this.logService.trace(`[DefaultAccount] unexpected status code ${response.res.statusCode} while fetching MCP registry data`);
|
||||
return undefined;
|
||||
return response.res.statusCode === 404 /* mcp not configured */
|
||||
? null
|
||||
: undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await asJson<IMcpRegistryResponse>(response);
|
||||
if (data) {
|
||||
this.logService.debug('Fetched MCP registry providers', data.mcp_registries);
|
||||
return data.mcp_registries[0];
|
||||
return data.mcp_registries[0] ?? null;
|
||||
}
|
||||
this.logService.debug('Failed to fetch MCP registry providers', 'No data returned');
|
||||
this.logService.debug('No MCP registry providers content found in response');
|
||||
return null;
|
||||
} catch (error) {
|
||||
this.logService.error('Failed to fetch MCP registry providers', getErrorMessage(error));
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async request(url: string, type: 'GET', body: undefined, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined>;
|
||||
@@ -681,11 +721,6 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (lastResponse.res.statusCode && lastResponse.res.statusCode !== 200) {
|
||||
this.logService.trace(`[DefaultAccount]: unexpected status code ${lastResponse.res.statusCode} for request`, url);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return lastResponse;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user