Path: blob/main/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts
5283 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 { equals } from '../../../../base/common/arrays.js';6import { Event, Emitter } from '../../../../base/common/event.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';9import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';10import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';11import { ExtensionType } from '../../../../platform/extensions/common/extensions.js';12import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';13import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';14import { IProductService } from '../../../../platform/product/common/productService.js';15import { IWorkbenchAssignmentService } from '../../assignment/common/assignmentService.js';16import { EnablementState, IWorkbenchExtensionEnablementService } from '../../extensionManagement/common/extensionManagement.js';17import { IExtensionService } from '../../extensions/common/extensions.js';1819export const IInlineCompletionsUnificationService = createDecorator<IInlineCompletionsUnificationService>('inlineCompletionsUnificationService');2021export interface IInlineCompletionsUnificationState {22codeUnification: boolean;23modelUnification: boolean;24extensionUnification: boolean;25expAssignments: string[];26}2728export interface IInlineCompletionsUnificationService {29readonly _serviceBrand: undefined;3031readonly state: IInlineCompletionsUnificationState;32readonly onDidStateChange: Event<void>;33}3435const CODE_UNIFICATION_PREFIX = 'cmp-cht-';36const EXTENSION_UNIFICATION_PREFIX = 'cmp-ext-';37const CODE_UNIFICATION_FF = 'inlineCompletionsUnificationCode';38const MODEL_UNIFICATION_FF = 'inlineCompletionsUnificationModel';3940export const isRunningUnificationExperiment = new RawContextKey<boolean>('isRunningUnificationExperiment', false);4142const ExtensionUnificationSetting = 'chat.extensionUnification.enabled';4344export class InlineCompletionsUnificationImpl extends Disposable implements IInlineCompletionsUnificationService {45readonly _serviceBrand: undefined;4647private _state = new InlineCompletionsUnificationState(false, false, false, []);48public get state(): IInlineCompletionsUnificationState { return this._state; }4950private isRunningUnificationExperiment;5152private readonly _onDidStateChange = this._register(new Emitter<void>());53public readonly onDidStateChange = this._onDidStateChange.event;5455private readonly _onDidChangeExtensionUnificationState = this._register(new Emitter<void>());56private readonly _onDidChangeExtensionUnificationSetting = this._register(new Emitter<void>());5758private readonly _completionsExtensionId: string | undefined;59private readonly _chatExtensionId: string | undefined;6061constructor(62@IWorkbenchAssignmentService private readonly _assignmentService: IWorkbenchAssignmentService,63@IContextKeyService private readonly _contextKeyService: IContextKeyService,64@IConfigurationService private readonly _configurationService: IConfigurationService,65@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,66@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,67@IExtensionService private readonly _extensionService: IExtensionService,68@IProductService productService: IProductService69) {70super();71this._completionsExtensionId = productService.defaultChatAgent?.extensionId.toLowerCase();72this._chatExtensionId = productService.defaultChatAgent?.chatExtensionId.toLowerCase();73const relevantExtensions = [this._completionsExtensionId, this._chatExtensionId].filter((id): id is string => !!id);7475this.isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService);7677this._assignmentService.addTelemetryAssignmentFilter({78exclude: (assignment) => assignment.startsWith(EXTENSION_UNIFICATION_PREFIX) && this._state.extensionUnification !== this._configurationService.getValue<boolean>(ExtensionUnificationSetting),79onDidChange: Event.any(this._onDidChangeExtensionUnificationState.event, this._onDidChangeExtensionUnificationSetting.event)80});8182this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {83if (extensions.some(ext => relevantExtensions.includes(ext.identifier.id.toLowerCase()))) {84this._update();85}86}));87this._register(this._configurationService.onDidChangeConfiguration(e => {88if (e.affectsConfiguration(ExtensionUnificationSetting)) {89this._update();90this._onDidChangeExtensionUnificationSetting.fire();91}92}));93this._register(this._extensionService.onDidChangeExtensions(({ added }) => {94if (added.some(ext => relevantExtensions.includes(ext.identifier.value.toLowerCase()))) {95this._update();96}97}));98this._register(this._assignmentService.onDidRefetchAssignments(() => this._update()));99this._update();100}101102private async _update(): Promise<void> {103const [codeUnificationFF, modelUnificationFF, extensionUnificationEnabled] = await Promise.all([104this._assignmentService.getTreatment<boolean>(CODE_UNIFICATION_FF),105this._assignmentService.getTreatment<boolean>(MODEL_UNIFICATION_FF),106this._isExtensionUnificationActive()107]);108109const extensionStatesMatchUnificationSetting = this._configurationService.getValue<boolean>(ExtensionUnificationSetting) === extensionUnificationEnabled;110111// Intentionally read the current experiments after fetching the treatments112const currentExperiments = await this._assignmentService.getCurrentExperiments();113const newState = new InlineCompletionsUnificationState(114codeUnificationFF === true,115modelUnificationFF === true,116extensionUnificationEnabled,117currentExperiments?.filter(exp => exp.startsWith(CODE_UNIFICATION_PREFIX) || (extensionStatesMatchUnificationSetting && exp.startsWith(EXTENSION_UNIFICATION_PREFIX))) ?? []118);119if (this._state.equals(newState)) {120return;121}122123const previousState = this._state;124this._state = newState;125this.isRunningUnificationExperiment.set(this._state.codeUnification || this._state.modelUnification || this._state.extensionUnification);126this._onDidStateChange.fire();127128if (previousState.extensionUnification !== this._state.extensionUnification) {129this._onDidChangeExtensionUnificationState.fire();130}131}132133private async _isExtensionUnificationActive(): Promise<boolean> {134if (!this._configurationService.getValue<boolean>(ExtensionUnificationSetting)) {135return false;136}137138if (!this._completionsExtensionId || !this._chatExtensionId) {139return false;140}141142const [completionsExtension, chatExtension, installedExtensions] = await Promise.all([143this._extensionService.getExtension(this._completionsExtensionId),144this._extensionService.getExtension(this._chatExtensionId),145this._extensionManagementService.getInstalled(ExtensionType.User)146]);147148if (!chatExtension || completionsExtension) {149return false;150}151152// Extension might be installed on remote and local153const completionExtensionInstalled = installedExtensions.filter(ext => ext.identifier.id.toLowerCase() === this._completionsExtensionId);154if (completionExtensionInstalled.length === 0) {155return true;156}157158const completionsExtensionDisabledByUnification = completionExtensionInstalled.some(ext => this._extensionEnablementService.getEnablementState(ext) === EnablementState.DisabledByUnification);159160return !!chatExtension && completionsExtensionDisabledByUnification;161}162}163164class InlineCompletionsUnificationState implements IInlineCompletionsUnificationState {165constructor(166public readonly codeUnification: boolean,167public readonly modelUnification: boolean,168public readonly extensionUnification: boolean,169public readonly expAssignments: string[]170) {171}172173equals(other: IInlineCompletionsUnificationState): boolean {174return this.codeUnification === other.codeUnification175&& this.modelUnification === other.modelUnification176&& this.extensionUnification === other.extensionUnification177&& equals(this.expAssignments, other.expAssignments);178}179}180181registerSingleton(IInlineCompletionsUnificationService, InlineCompletionsUnificationImpl, InstantiationType.Delayed);182183184