Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/inlineCompletions/common/inlineCompletionsUnification.ts
5283 views
1
/*---------------------------------------------------------------------------------------------
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 { equals } from '../../../../base/common/arrays.js';
7
import { Event, Emitter } from '../../../../base/common/event.js';
8
import { Disposable } from '../../../../base/common/lifecycle.js';
9
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
10
import { IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
11
import { IExtensionManagementService } from '../../../../platform/extensionManagement/common/extensionManagement.js';
12
import { ExtensionType } from '../../../../platform/extensions/common/extensions.js';
13
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
14
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
15
import { IProductService } from '../../../../platform/product/common/productService.js';
16
import { IWorkbenchAssignmentService } from '../../assignment/common/assignmentService.js';
17
import { EnablementState, IWorkbenchExtensionEnablementService } from '../../extensionManagement/common/extensionManagement.js';
18
import { IExtensionService } from '../../extensions/common/extensions.js';
19
20
export const IInlineCompletionsUnificationService = createDecorator<IInlineCompletionsUnificationService>('inlineCompletionsUnificationService');
21
22
export interface IInlineCompletionsUnificationState {
23
codeUnification: boolean;
24
modelUnification: boolean;
25
extensionUnification: boolean;
26
expAssignments: string[];
27
}
28
29
export interface IInlineCompletionsUnificationService {
30
readonly _serviceBrand: undefined;
31
32
readonly state: IInlineCompletionsUnificationState;
33
readonly onDidStateChange: Event<void>;
34
}
35
36
const CODE_UNIFICATION_PREFIX = 'cmp-cht-';
37
const EXTENSION_UNIFICATION_PREFIX = 'cmp-ext-';
38
const CODE_UNIFICATION_FF = 'inlineCompletionsUnificationCode';
39
const MODEL_UNIFICATION_FF = 'inlineCompletionsUnificationModel';
40
41
export const isRunningUnificationExperiment = new RawContextKey<boolean>('isRunningUnificationExperiment', false);
42
43
const ExtensionUnificationSetting = 'chat.extensionUnification.enabled';
44
45
export class InlineCompletionsUnificationImpl extends Disposable implements IInlineCompletionsUnificationService {
46
readonly _serviceBrand: undefined;
47
48
private _state = new InlineCompletionsUnificationState(false, false, false, []);
49
public get state(): IInlineCompletionsUnificationState { return this._state; }
50
51
private isRunningUnificationExperiment;
52
53
private readonly _onDidStateChange = this._register(new Emitter<void>());
54
public readonly onDidStateChange = this._onDidStateChange.event;
55
56
private readonly _onDidChangeExtensionUnificationState = this._register(new Emitter<void>());
57
private readonly _onDidChangeExtensionUnificationSetting = this._register(new Emitter<void>());
58
59
private readonly _completionsExtensionId: string | undefined;
60
private readonly _chatExtensionId: string | undefined;
61
62
constructor(
63
@IWorkbenchAssignmentService private readonly _assignmentService: IWorkbenchAssignmentService,
64
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
65
@IConfigurationService private readonly _configurationService: IConfigurationService,
66
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
67
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
68
@IExtensionService private readonly _extensionService: IExtensionService,
69
@IProductService productService: IProductService
70
) {
71
super();
72
this._completionsExtensionId = productService.defaultChatAgent?.extensionId.toLowerCase();
73
this._chatExtensionId = productService.defaultChatAgent?.chatExtensionId.toLowerCase();
74
const relevantExtensions = [this._completionsExtensionId, this._chatExtensionId].filter((id): id is string => !!id);
75
76
this.isRunningUnificationExperiment = isRunningUnificationExperiment.bindTo(this._contextKeyService);
77
78
this._assignmentService.addTelemetryAssignmentFilter({
79
exclude: (assignment) => assignment.startsWith(EXTENSION_UNIFICATION_PREFIX) && this._state.extensionUnification !== this._configurationService.getValue<boolean>(ExtensionUnificationSetting),
80
onDidChange: Event.any(this._onDidChangeExtensionUnificationState.event, this._onDidChangeExtensionUnificationSetting.event)
81
});
82
83
this._register(this._extensionEnablementService.onEnablementChanged((extensions) => {
84
if (extensions.some(ext => relevantExtensions.includes(ext.identifier.id.toLowerCase()))) {
85
this._update();
86
}
87
}));
88
this._register(this._configurationService.onDidChangeConfiguration(e => {
89
if (e.affectsConfiguration(ExtensionUnificationSetting)) {
90
this._update();
91
this._onDidChangeExtensionUnificationSetting.fire();
92
}
93
}));
94
this._register(this._extensionService.onDidChangeExtensions(({ added }) => {
95
if (added.some(ext => relevantExtensions.includes(ext.identifier.value.toLowerCase()))) {
96
this._update();
97
}
98
}));
99
this._register(this._assignmentService.onDidRefetchAssignments(() => this._update()));
100
this._update();
101
}
102
103
private async _update(): Promise<void> {
104
const [codeUnificationFF, modelUnificationFF, extensionUnificationEnabled] = await Promise.all([
105
this._assignmentService.getTreatment<boolean>(CODE_UNIFICATION_FF),
106
this._assignmentService.getTreatment<boolean>(MODEL_UNIFICATION_FF),
107
this._isExtensionUnificationActive()
108
]);
109
110
const extensionStatesMatchUnificationSetting = this._configurationService.getValue<boolean>(ExtensionUnificationSetting) === extensionUnificationEnabled;
111
112
// Intentionally read the current experiments after fetching the treatments
113
const currentExperiments = await this._assignmentService.getCurrentExperiments();
114
const newState = new InlineCompletionsUnificationState(
115
codeUnificationFF === true,
116
modelUnificationFF === true,
117
extensionUnificationEnabled,
118
currentExperiments?.filter(exp => exp.startsWith(CODE_UNIFICATION_PREFIX) || (extensionStatesMatchUnificationSetting && exp.startsWith(EXTENSION_UNIFICATION_PREFIX))) ?? []
119
);
120
if (this._state.equals(newState)) {
121
return;
122
}
123
124
const previousState = this._state;
125
this._state = newState;
126
this.isRunningUnificationExperiment.set(this._state.codeUnification || this._state.modelUnification || this._state.extensionUnification);
127
this._onDidStateChange.fire();
128
129
if (previousState.extensionUnification !== this._state.extensionUnification) {
130
this._onDidChangeExtensionUnificationState.fire();
131
}
132
}
133
134
private async _isExtensionUnificationActive(): Promise<boolean> {
135
if (!this._configurationService.getValue<boolean>(ExtensionUnificationSetting)) {
136
return false;
137
}
138
139
if (!this._completionsExtensionId || !this._chatExtensionId) {
140
return false;
141
}
142
143
const [completionsExtension, chatExtension, installedExtensions] = await Promise.all([
144
this._extensionService.getExtension(this._completionsExtensionId),
145
this._extensionService.getExtension(this._chatExtensionId),
146
this._extensionManagementService.getInstalled(ExtensionType.User)
147
]);
148
149
if (!chatExtension || completionsExtension) {
150
return false;
151
}
152
153
// Extension might be installed on remote and local
154
const completionExtensionInstalled = installedExtensions.filter(ext => ext.identifier.id.toLowerCase() === this._completionsExtensionId);
155
if (completionExtensionInstalled.length === 0) {
156
return true;
157
}
158
159
const completionsExtensionDisabledByUnification = completionExtensionInstalled.some(ext => this._extensionEnablementService.getEnablementState(ext) === EnablementState.DisabledByUnification);
160
161
return !!chatExtension && completionsExtensionDisabledByUnification;
162
}
163
}
164
165
class InlineCompletionsUnificationState implements IInlineCompletionsUnificationState {
166
constructor(
167
public readonly codeUnification: boolean,
168
public readonly modelUnification: boolean,
169
public readonly extensionUnification: boolean,
170
public readonly expAssignments: string[]
171
) {
172
}
173
174
equals(other: IInlineCompletionsUnificationState): boolean {
175
return this.codeUnification === other.codeUnification
176
&& this.modelUnification === other.modelUnification
177
&& this.extensionUnification === other.extensionUnification
178
&& equals(this.expAssignments, other.expAssignments);
179
}
180
}
181
182
registerSingleton(IInlineCompletionsUnificationService, InlineCompletionsUnificationImpl, InstantiationType.Delayed);
183
184