allow policy config to compute the policy value for account based policies (#263410)

This commit is contained in:
Sandeep Somavarapu
2025-08-26 15:38:22 +02:00
committed by GitHub
parent 0a565d144f
commit 8791650e81
10 changed files with 75 additions and 129 deletions
+26
View File
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IDefaultAccount {
readonly sessionId: string;
readonly enterprise: boolean;
readonly access_type_sku?: string;
readonly assigned_date?: string;
readonly can_signup_for_limited?: boolean;
readonly chat_enabled?: boolean;
readonly chat_preview_features_enabled?: boolean;
readonly mcp?: boolean;
readonly analytics_tracking_id?: string;
readonly limited_user_quotas?: {
readonly chat: number;
readonly completions: number;
};
readonly monthly_quotas?: {
readonly chat: number;
readonly completions: number;
};
readonly limited_user_reset_date?: string;
readonly chat_agent_enabled?: boolean;
}
+6 -19
View File
@@ -3,14 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export type PolicyName = string;
import { IDefaultAccount } from './defaultAccount.js';
export enum PolicyTag {
Account = 'ACCOUNT',
MCP = 'MCP',
Preview = 'PREVIEW',
Agent = 'AGENT',
}
export type PolicyName = string;
export interface IPolicy {
@@ -36,18 +31,10 @@ export interface IPolicy {
* this value determines what value the feature receives.
*
* For example:
* - If `defaultValue: true`, the feature's setting is locked to `true` WHEN the policy is in effect.
* - If `defaultValue: 'foo'`, the feature's setting is locked to 'foo' WHEN the policy is in effect.
* - If evaluated value is `true`, the feature's setting is locked to `true` WHEN the policy is in effect.
* - If evaluated value is `foo`, the feature's setting is locked to 'foo' WHEN the policy is in effect.
*
* If omitted, 'false' is the assumed value.
*
* Note: This is unrelated to the default value of the VS Code setting itself. This specifically controls
* the value of an account-based feature's setting WHEN the policy is overriding it.
* If `undefined`, the feature's setting is not locked and can be overridden by other means.
*/
readonly defaultValue?: string | number | boolean;
/**
* Tags for categorizing policies
*/
readonly tags?: PolicyTag[];
readonly value?: (account: IDefaultAccount) => string | number | boolean | undefined;
}
@@ -137,12 +137,11 @@ export class PolicyConfiguration extends Disposable implements IPolicyConfigurat
this.logService.warn(`Policy ${config.policy.name} has unsupported type ${config.type}`);
continue;
}
const { defaultValue, tags } = config.policy;
const { value } = config.policy;
keys.push(key);
policyDefinitions[config.policy.name] = {
type: config.type === 'number' ? 'number' : config.type === 'boolean' ? 'boolean' : 'string',
tags,
defaultValue,
value,
};
}
}
+6 -2
View File
@@ -4,14 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import { IStringDictionary } from '../../../base/common/collections.js';
import { IDefaultAccount } from '../../../base/common/defaultAccount.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { PolicyName, PolicyTag } from '../../../base/common/policy.js';
import { PolicyName } from '../../../base/common/policy.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
export type PolicyValue = string | number | boolean;
export type PolicyDefinition = { type: 'string' | 'number' | 'boolean'; defaultValue?: string | number | boolean; tags?: PolicyTag[] };
export type PolicyDefinition = {
type: 'string' | 'number' | 'boolean';
value?: (account: IDefaultAccount) => string | number | boolean | undefined;
};
export const IPolicyService = createDecorator<IPolicyService>('policy');
@@ -9,7 +9,6 @@ import { MarkdownString, isMarkdownString } from '../../../../base/common/htmlCo
import { Disposable } from '../../../../base/common/lifecycle.js';
import { Schemas } from '../../../../base/common/network.js';
import { isMacintosh } from '../../../../base/common/platform.js';
import { PolicyTag } from '../../../../base/common/policy.js';
import { assertDefined } from '../../../../base/common/types.js';
import { registerEditorFeature } from '../../../../editor/common/editorFeatures.js';
import * as nls from '../../../../nls.js';
@@ -245,8 +244,7 @@ configurationRegistry.registerConfiguration({
policy: {
name: 'ChatToolsAutoApprove',
minimumVersion: '1.99',
defaultValue: false,
tags: [PolicyTag.Account, PolicyTag.Preview]
value: (account) => account.chat_preview_features_enabled === false ? false : undefined,
}
},
[ChatConfiguration.AutoApproveEdits]: {
@@ -316,7 +314,7 @@ configurationRegistry.registerConfiguration({
policy: {
name: 'ChatMCP',
minimumVersion: '1.99',
tags: [PolicyTag.Account, PolicyTag.MCP]
value: (account) => account.mcp === false ? false : undefined,
}
},
[mcpAutoStartConfig]: {
@@ -399,7 +397,7 @@ configurationRegistry.registerConfiguration({
policy: {
name: 'ChatAgentMode',
minimumVersion: '1.99',
tags: [PolicyTag.Account, PolicyTag.Agent]
value: (account) => account.chat_agent_enabled === false ? false : undefined,
}
},
[ChatConfiguration.EnableMath]: {
@@ -19,6 +19,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js';
import { Barrier } from '../../../../base/common/async.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { getErrorMessage } from '../../../../base/common/errors.js';
import { IDefaultAccount } from '../../../../base/common/defaultAccount.js';
export const DEFAULT_ACCOUNT_SIGN_IN_COMMAND = 'workbench.actions.accounts.signIn';
@@ -30,28 +31,6 @@ const enum DefaultAccountStatus {
const CONTEXT_DEFAULT_ACCOUNT_STATE = new RawContextKey<string>('defaultAccountStatus', DefaultAccountStatus.Uninitialized);
export interface IDefaultAccount {
readonly sessionId: string;
readonly enterprise: boolean;
readonly access_type_sku?: string;
readonly assigned_date?: string;
readonly can_signup_for_limited?: boolean;
readonly chat_enabled?: boolean;
readonly chat_preview_features_enabled?: boolean;
readonly mcp?: boolean;
readonly analytics_tracking_id?: string;
readonly limited_user_quotas?: {
readonly chat: number;
readonly completions: number;
};
readonly monthly_quotas?: {
readonly chat: number;
readonly completions: number;
};
readonly limited_user_reset_date?: string;
readonly chat_agent_enabled?: boolean;
}
interface IChatEntitlementsResponse {
readonly access_type_sku: string;
readonly assigned_date: string;
@@ -20,10 +20,11 @@ import { IProductService } from '../../../../platform/product/common/productServ
import { asJson, IRequestService } from '../../../../platform/request/common/request.js';
import { IStorageService } from '../../../../platform/storage/common/storage.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IDefaultAccount, IDefaultAccountService } from '../../accounts/common/defaultAccount.js';
import { IDefaultAccountService } from '../../accounts/common/defaultAccount.js';
import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import { IHostService } from '../../host/browser/host.js';
import { IDefaultAccount } from '../../../../base/common/defaultAccount.js';
export class WorkbenchExtensionGalleryManifestService extends ExtensionGalleryManifestService implements IExtensionGalleryManifestService {
@@ -4,24 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { IStringDictionary } from '../../../../base/common/collections.js';
import { equals } from '../../../../base/common/objects.js';
import { PolicyTag } from '../../../../base/common/policy.js';
import { IDefaultAccount } from '../../../../base/common/defaultAccount.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { AbstractPolicyService, IPolicyService, PolicyDefinition } from '../../../../platform/policy/common/policy.js';
import { IDefaultAccountService } from '../../accounts/common/defaultAccount.js';
interface IAccountPolicy {
readonly chatPreviewFeaturesEnabled: boolean;
readonly mcpEnabled: boolean;
readonly chatAgentEnabled: boolean;
}
export class AccountPolicyService extends AbstractPolicyService implements IPolicyService {
private accountPolicy: IAccountPolicy = {
chatPreviewFeaturesEnabled: true,
mcpEnabled: true,
chatAgentEnabled: true
};
private account: IDefaultAccount | null = null;
constructor(
@ILogService private readonly logService: ILogService,
@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService
@@ -30,66 +22,32 @@ export class AccountPolicyService extends AbstractPolicyService implements IPoli
this.defaultAccountService.getDefaultAccount()
.then(account => {
this._update({
chatPreviewFeaturesEnabled: account?.chat_preview_features_enabled ?? true,
mcpEnabled: account?.mcp ?? true,
chatAgentEnabled: account?.chat_agent_enabled ?? true
});
this._register(this.defaultAccountService.onDidChangeDefaultAccount(
account => this._update({
chatPreviewFeaturesEnabled: account?.chat_preview_features_enabled ?? true,
mcpEnabled: account?.mcp ?? true,
chatAgentEnabled: account?.chat_agent_enabled ?? true
})
));
this.account = account;
this._updatePolicyDefinitions(this.policyDefinitions);
this._register(this.defaultAccountService.onDidChangeDefaultAccount(account => {
this.account = account;
this._updatePolicyDefinitions(this.policyDefinitions);
}));
});
}
private _update(updatedPolicy: IAccountPolicy): void {
if (!equals(this.accountPolicy, updatedPolicy)) {
this.accountPolicy = updatedPolicy;
this._updatePolicyDefinitions(this.policyDefinitions);
}
}
protected async _updatePolicyDefinitions(policyDefinitions: IStringDictionary<PolicyDefinition>): Promise<void> {
this.logService.trace(`AccountPolicyService#_updatePolicyDefinitions: Got ${Object.keys(policyDefinitions).length} policy definitions`);
const updated: string[] = [];
const updateIfNeeded = (key: string, policy: PolicyDefinition, isFeatureEnabled: boolean): void => {
if (isFeatureEnabled) {
// Clear the policy if it is set
for (const key in policyDefinitions) {
const policy = policyDefinitions[key];
const policyValue = this.account && policy.value ? policy.value(this.account) : undefined;
if (policyValue !== undefined) {
if (this.policies.get(key) !== policyValue) {
this.policies.set(key, policyValue);
updated.push(key);
}
} else {
if (this.policies.has(key)) {
this.policies.delete(key);
updated.push(key);
}
} else {
// Enforce the defaultValue if not already set
const updatedValue = policy.defaultValue === undefined ? false : policy.defaultValue;
if (this.policies.get(key) !== updatedValue) {
this.policies.set(key, updatedValue);
updated.push(key);
}
}
};
const hasAllTags = (policy: PolicyDefinition, tags: PolicyTag[]): boolean | undefined => {
return policy.tags && tags.every(tag => policy.tags!.includes(tag));
};
for (const key in policyDefinitions) {
const policy = policyDefinitions[key];
// Map chat preview features with ACCOUNT + PREVIEW tags
if (hasAllTags(policy, [PolicyTag.Account, PolicyTag.Preview])) {
updateIfNeeded(key, policy, this.accountPolicy?.chatPreviewFeaturesEnabled);
}
// Map MCP feature with MCP tag
else if (hasAllTags(policy, [PolicyTag.Account, PolicyTag.MCP])) {
updateIfNeeded(key, policy, this.accountPolicy?.mcpEnabled);
}
else if (hasAllTags(policy, [PolicyTag.Account, PolicyTag.Agent])) {
updateIfNeeded(key, policy, this.accountPolicy?.chatAgentEnabled);
}
}
@@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { PolicyTag } from '../../../../../base/common/policy.js';
import { NullLogService } from '../../../../../platform/log/common/log.js';
import { DefaultAccountService, IDefaultAccount, IDefaultAccountService } from '../../../accounts/common/defaultAccount.js';
import { DefaultAccountService, IDefaultAccountService } from '../../../accounts/common/defaultAccount.js';
import { AccountPolicyService } from '../../common/accountPolicyService.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { Registry } from '../../../../../platform/registry/common/platform.js';
import { Extensions, IConfigurationNode, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js';
import { DefaultConfiguration, PolicyConfiguration } from '../../../../../platform/configuration/common/configurations.js';
import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js';
const BASE_DEFAULT_ACCOUNT: IDefaultAccount = {
enterprise: false,
@@ -47,8 +47,7 @@ suite('AccountPolicyService', () => {
policy: {
name: 'PolicySettingB',
minimumVersion: '1.0.0',
defaultValue: "policyValueB",
tags: [PolicyTag.Account, PolicyTag.Preview]
value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined,
}
},
'setting.C': {
@@ -57,8 +56,7 @@ suite('AccountPolicyService', () => {
policy: {
name: 'PolicySettingC',
minimumVersion: '1.0.0',
defaultValue: JSON.stringify(['policyValueC1', 'policyValueC2']),
tags: [PolicyTag.Account, PolicyTag.Preview]
value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined,
}
},
'setting.D': {
@@ -67,8 +65,7 @@ suite('AccountPolicyService', () => {
policy: {
name: 'PolicySettingD',
minimumVersion: '1.0.0',
defaultValue: false,
tags: [PolicyTag.Account, PolicyTag.Preview]
value: account => account.chat_preview_features_enabled === false ? false : undefined,
}
},
'setting.E': {
@@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { PolicyTag } from '../../../../../base/common/policy.js';
import { NullLogService } from '../../../../../platform/log/common/log.js';
import { DefaultAccountService, IDefaultAccount, IDefaultAccountService } from '../../../accounts/common/defaultAccount.js';
import { DefaultAccountService, IDefaultAccountService } from '../../../accounts/common/defaultAccount.js';
import { AccountPolicyService } from '../../common/accountPolicyService.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { Registry } from '../../../../../platform/registry/common/platform.js';
@@ -19,6 +18,7 @@ import { IFileService } from '../../../../../platform/files/common/files.js';
import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js';
import { FileService } from '../../../../../platform/files/common/fileService.js';
import { VSBuffer } from '../../../../../base/common/buffer.js';
import { IDefaultAccount } from '../../../../../base/common/defaultAccount.js';
const BASE_DEFAULT_ACCOUNT: IDefaultAccount = {
enterprise: false,
@@ -56,8 +56,7 @@ suite('MultiplexPolicyService', () => {
policy: {
name: 'PolicySettingB',
minimumVersion: '1.0.0',
defaultValue: "policyValueB",
tags: [PolicyTag.Account, PolicyTag.Preview]
value: account => account.chat_preview_features_enabled === false ? 'policyValueB' : undefined,
}
},
'setting.C': {
@@ -66,8 +65,7 @@ suite('MultiplexPolicyService', () => {
policy: {
name: 'PolicySettingC',
minimumVersion: '1.0.0',
defaultValue: JSON.stringify(['policyValueC1', 'policyValueC2']),
tags: [PolicyTag.Account, PolicyTag.Preview]
value: account => account.chat_preview_features_enabled === false ? JSON.stringify(['policyValueC1', 'policyValueC2']) : undefined,
}
},
'setting.D': {
@@ -76,8 +74,7 @@ suite('MultiplexPolicyService', () => {
policy: {
name: 'PolicySettingD',
minimumVersion: '1.0.0',
defaultValue: false,
tags: [PolicyTag.Account, PolicyTag.Preview]
value: account => account.chat_preview_features_enabled === false ? false : undefined,
}
},
'setting.E': {