Skip to content

Commit 6484cd7

Browse files
roblourenslramos15
andauthored
Merge pull request #1860 from microsoft/roblou/revert-chat-activation (#1878)
Restore activationBlockers on auth Co-authored-by: Logan Ramos <[email protected]>
1 parent daf51a0 commit 6484cd7

File tree

6 files changed

+94
-49
lines changed

6 files changed

+94
-49
lines changed

src/extension/common/contributions.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { ILogService } from '../../platform/log/common/logService';
77
import { Disposable, isDisposable } from '../../util/vs/base/common/lifecycle';
8+
import { StopWatch } from '../../util/vs/base/common/stopwatch';
89
import { IInstantiationService, ServicesAccessor } from '../../util/vs/platform/instantiation/common/instantiation';
910

1011
export interface IExtensionContribution {
@@ -15,6 +16,12 @@ export interface IExtensionContribution {
1516
* Dispose of the contribution.
1617
*/
1718
dispose?(): void;
19+
20+
/**
21+
* A promise that the extension `activate` method will wait on before completing.
22+
* USE this carefully as it will delay startup of our extension.
23+
*/
24+
activationBlocker?: Promise<any>;
1825
}
1926

2027
export interface IExtensionContributionFactory {
@@ -31,6 +38,8 @@ export function asContributionFactory(ctor: { new(...args: any[]): any }): IExte
3138
}
3239

3340
export class ContributionCollection extends Disposable {
41+
private readonly allActivationBlockers: Promise<any>[] = [];
42+
3443
constructor(
3544
contribs: IExtensionContributionFactory[],
3645
@ILogService logService: ILogService,
@@ -46,9 +55,22 @@ export class ContributionCollection extends Disposable {
4655
if (isDisposable(instance)) {
4756
this._register(instance);
4857
}
58+
59+
if (instance?.activationBlocker) {
60+
const sw = StopWatch.create();
61+
const id = instance.id || 'UNKNOWN';
62+
this.allActivationBlockers.push(instance.activationBlocker.finally(() => {
63+
logService.info(`activationBlocker from '${id}' took for ${Math.round(sw.elapsed())}ms`);
64+
}));
65+
}
4966
} catch (error) {
5067
logService.error(error, `Error while loading contribution`);
5168
}
5269
}
5370
}
71+
72+
async waitForActivationBlockers(): Promise<void> {
73+
// WAIT for all activation blockers to complete
74+
await Promise.allSettled(this.allActivationBlockers);
75+
}
5476
}

src/extension/conversation/vscode-node/chatParticipants.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import { IInteractionService } from '../../../platform/chat/common/interactionSe
1010
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
1111
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1212
import { IOctoKitService } from '../../../platform/github/common/githubService';
13-
import { ILogService } from '../../../platform/log/common/logService';
1413
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
15-
import { DeferredPromise } from '../../../util/vs/base/common/async';
1614
import { Event, Relay } from '../../../util/vs/base/common/event';
1715
import { DisposableStore, IDisposable } from '../../../util/vs/base/common/lifecycle';
1816
import { autorun } from '../../../util/vs/base/common/observableInternal';
@@ -30,18 +28,18 @@ import { getAdditionalWelcomeMessage } from './welcomeMessageProvider';
3028
export class ChatAgentService implements IChatAgentService {
3129
declare readonly _serviceBrand: undefined;
3230

33-
private _lastChatAgents: ChatParticipants | undefined; // will be cleared when disposed
31+
private _lastChatAgents: ChatAgents | undefined; // will be cleared when disposed
3432

3533
constructor(
3634
@IInstantiationService private readonly instantiationService: IInstantiationService,
3735
) { }
3836

39-
public debugGetCurrentChatAgents(): ChatParticipants | undefined {
37+
public debugGetCurrentChatAgents(): ChatAgents | undefined {
4038
return this._lastChatAgents;
4139
}
4240

4341
register(): IDisposable {
44-
const chatAgents = this.instantiationService.createInstance(ChatParticipants);
42+
const chatAgents = this.instantiationService.createInstance(ChatAgents);
4543
chatAgents.register();
4644
this._lastChatAgents = chatAgents;
4745
return {
@@ -53,13 +51,11 @@ export class ChatAgentService implements IChatAgentService {
5351
}
5452
}
5553

56-
class ChatParticipants implements IDisposable {
54+
class ChatAgents implements IDisposable {
5755
private readonly _disposables = new DisposableStore();
5856

5957
private additionalWelcomeMessage: vscode.MarkdownString | undefined;
6058

61-
private requestBlocker: Promise<void>;
62-
6359
constructor(
6460
@IOctoKitService private readonly octoKitService: IOctoKitService,
6561
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
@@ -71,24 +67,7 @@ class ChatParticipants implements IDisposable {
7167
@IChatQuotaService private readonly _chatQuotaService: IChatQuotaService,
7268
@IConfigurationService private readonly configurationService: IConfigurationService,
7369
@IExperimentationService private readonly experimentationService: IExperimentationService,
74-
@ILogService private readonly logService: ILogService,
75-
) {
76-
// TODO@roblourens This may not be necessary - the point for now is to match the old behavior of blocking chat until auth completes
77-
const requestBlockerDeferred = new DeferredPromise<void>();
78-
this.requestBlocker = requestBlockerDeferred.p;
79-
if (authenticationService.copilotToken) {
80-
this.logService.debug(`ChatParticipants: Copilot token already available`);
81-
requestBlockerDeferred.complete();
82-
} else {
83-
this._disposables.add(authenticationService.onDidAuthenticationChange(async () => {
84-
const hasSession = !!authenticationService.copilotToken;
85-
this.logService.debug(`ChatParticipants: onDidAuthenticationChange has token: ${hasSession}`);
86-
if (hasSession) {
87-
requestBlockerDeferred.complete();
88-
}
89-
}));
90-
}
91-
}
70+
) { }
9271

9372
dispose() {
9473
this._disposables.dispose();
@@ -280,7 +259,6 @@ Learn more about [GitHub Copilot](https://docs.github.com/copilot/using-github-c
280259

281260
private getChatParticipantHandler(id: string, name: string, defaultIntentIdOrGetter: IntentOrGetter, onRequestPaused: Event<vscode.ChatParticipantPauseStateEvent>): vscode.ChatExtendedRequestHandler {
282261
return async (request, context, stream, token): Promise<vscode.ChatResult> => {
283-
await this.requestBlocker;
284262

285263
// If we need privacy confirmation, i.e with 3rd party models. We will return a confirmation response and return early
286264
const privacyConfirmation = await this.requestPolicyConfirmation(request, stream);

src/extension/conversation/vscode-node/conversationFeature.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ILogService } from '../../../platform/log/common/logService';
1818
import { ISettingsEditorSearchService } from '../../../platform/settingsEditor/common/settingsEditorSearchService';
1919
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
2020
import { isUri } from '../../../util/common/types';
21+
import { DeferredPromise } from '../../../util/vs/base/common/async';
2122
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
2223
import { DisposableStore, IDisposable, combinedDisposable } from '../../../util/vs/base/common/lifecycle';
2324
import { URI } from '../../../util/vs/base/common/uri';
@@ -61,6 +62,7 @@ export class ConversationFeature implements IExtensionContribution {
6162
private _settingsSearchProviderRegistered = false;
6263

6364
readonly id = 'conversationFeature';
65+
readonly activationBlocker?: Promise<any>;
6466

6567
constructor(
6668
@IInstantiationService private instantiationService: IInstantiationService,
@@ -85,7 +87,25 @@ export class ConversationFeature implements IExtensionContribution {
8587
// Register Copilot token listener
8688
this.registerCopilotTokenListener();
8789

88-
this.activated = true;
90+
const activationBlockerDeferred = new DeferredPromise<void>();
91+
this.activationBlocker = activationBlockerDeferred.p;
92+
if (authenticationService.copilotToken) {
93+
this.logService.debug(`ConversationFeature: Copilot token already available`);
94+
this.activated = true;
95+
activationBlockerDeferred.complete();
96+
}
97+
98+
this._disposables.add(authenticationService.onDidAuthenticationChange(async () => {
99+
const hasSession = !!authenticationService.copilotToken;
100+
this.logService.debug(`ConversationFeature: onDidAuthenticationChange has token: ${hasSession}`);
101+
if (hasSession) {
102+
this.activated = true;
103+
} else {
104+
this.activated = false;
105+
}
106+
107+
activationBlockerDeferred.complete();
108+
}));
89109
}
90110

91111
get enabled() {

src/extension/conversation/vscode-node/languageModelAccess.ts

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { isEncryptedThinkingDelta } from '../../../platform/thinking/common/thin
2929
import { BaseTokensPerCompletion } from '../../../platform/tokenizer/node/tokenizer';
3030
import { TelemetryCorrelationId } from '../../../util/common/telemetryCorrelationId';
3131
import { Emitter } from '../../../util/vs/base/common/event';
32-
import { Disposable } from '../../../util/vs/base/common/lifecycle';
32+
import { Disposable, MutableDisposable } from '../../../util/vs/base/common/lifecycle';
3333
import { isDefined, isNumber, isString, isStringArray } from '../../../util/vs/base/common/types';
3434
import { localize } from '../../../util/vs/nls';
3535
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
@@ -44,6 +44,8 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
4444

4545
readonly id = 'languageModelAccess';
4646

47+
readonly activationBlocker?: Promise<any>;
48+
4749
private readonly _onDidChange = this._register(new Emitter<void>());
4850
private _currentModels: vscode.LanguageModelChatInformation[] = []; // Store current models for reference
4951
private _chatEndpoints: IChatEndpoint[] = [];
@@ -71,8 +73,10 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
7173
}
7274

7375
// initial
74-
this._registerChatProvider();
75-
this._registerEmbeddings();
76+
this.activationBlocker = Promise.all([
77+
this._registerChatProvider(),
78+
this._registerEmbeddings(),
79+
]);
7680
}
7781

7882
override dispose(): void {
@@ -83,7 +87,7 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
8387
return this._currentModels;
8488
}
8589

86-
private _registerChatProvider(): void {
90+
private async _registerChatProvider(): Promise<void> {
8791
const provider: vscode.LanguageModelChatProvider = {
8892
onDidChangeLanguageModelChatInformation: this._onDidChange.event,
8993
provideLanguageModelChatInformation: this._provideLanguageModelChatInfo.bind(this),
@@ -256,22 +260,34 @@ export class LanguageModelAccess extends Disposable implements IExtensionContrib
256260

257261
private async _registerEmbeddings(): Promise<void> {
258262

259-
const embeddingsComputer = this._embeddingsComputer;
260-
const embeddingType = EmbeddingType.text3small_512;
261-
const model = getWellKnownEmbeddingTypeInfo(embeddingType)?.model;
262-
if (!model) {
263-
throw new Error(`No model found for embedding type ${embeddingType.id}`);
264-
}
263+
const dispo = this._register(new MutableDisposable());
264+
265265

266-
const that = this;
267-
this._register(vscode.lm.registerEmbeddingsProvider(`copilot.${model}`, new class implements vscode.EmbeddingsProvider {
268-
async provideEmbeddings(input: string[], token: vscode.CancellationToken): Promise<vscode.Embedding[]> {
269-
await that._getToken();
266+
const update = async () => {
270267

271-
const result = await embeddingsComputer.computeEmbeddings(embeddingType, input, {}, new TelemetryCorrelationId('EmbeddingsProvider::provideEmbeddings'), token);
272-
return result.values.map(embedding => ({ values: embedding.value.slice(0) }));
268+
if (!await this._getToken()) {
269+
dispo.clear();
270+
return;
273271
}
274-
}));
272+
273+
const embeddingsComputer = this._embeddingsComputer;
274+
const embeddingType = EmbeddingType.text3small_512;
275+
const model = getWellKnownEmbeddingTypeInfo(embeddingType)?.model;
276+
if (!model) {
277+
throw new Error(`No model found for embedding type ${embeddingType.id}`);
278+
}
279+
280+
dispo.clear();
281+
dispo.value = vscode.lm.registerEmbeddingsProvider(`copilot.${model}`, new class implements vscode.EmbeddingsProvider {
282+
async provideEmbeddings(input: string[], token: vscode.CancellationToken): Promise<vscode.Embedding[]> {
283+
const result = await embeddingsComputer.computeEmbeddings(embeddingType, input, {}, new TelemetryCorrelationId('EmbeddingsProvider::provideEmbeddings'), token);
284+
return result.values.map(embedding => ({ values: embedding.value.slice(0) }));
285+
}
286+
});
287+
};
288+
289+
this._register(this._authenticationService.onDidAuthenticationChange(() => update()));
290+
await update();
275291
}
276292

277293
private async _getToken(): Promise<CopilotToken | undefined> {

src/extension/conversation/vscode-node/test/conversationFeature.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ suite('Conversation feature test suite', function () {
7070
}
7171
});
7272

73+
test("If the 'interactive' version does not match, the feature is not enabled and not activated", function () {
74+
const conversationFeature = instaService.createInstance(ConversationFeature);
75+
try {
76+
assert.deepStrictEqual(conversationFeature.enabled, false);
77+
assert.deepStrictEqual(conversationFeature.activated, false);
78+
} finally {
79+
conversationFeature.dispose();
80+
}
81+
});
82+
7383
test('The feature is enabled and activated in test mode', function () {
7484
const conversationFeature = instaService.createInstance(ConversationFeature);
7585
try {

src/extension/extension/vscode/extension.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { isScenarioAutomation } from '../../../platform/env/common/envService';
99
import { isProduction } from '../../../platform/env/common/packagejson';
1010
import { IHeatmapService } from '../../../platform/heatmap/common/heatmapService';
1111
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
12-
import { ILogService } from '../../../platform/log/common/logService';
1312
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
1413
import { IInstantiationServiceBuilder, InstantiationServiceBuilder } from '../../../util/common/services';
1514
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
@@ -64,16 +63,16 @@ export async function baseActivate(configuration: IExtensionActivationConfigurat
6463

6564
await instantiationService.invokeFunction(async accessor => {
6665
const expService = accessor.get(IExperimentationService);
67-
const logService = accessor.get(ILogService);
6866

6967
// Await intialization of exp service. This ensure cache is fresh.
7068
// It will then auto refresh every 30 minutes after that.
7169
await expService.hasTreatments();
7270

71+
// THIS is awaited because some contributions can block activation
72+
// via `IExtensionContribution#activationBlocker`
7373
const contributions = instantiationService.createInstance(ContributionCollection, configuration.contributions);
7474
context.subscriptions.push(contributions);
75-
76-
logService.trace('Copilot Chat extension activated');
75+
await contributions.waitForActivationBlockers();
7776
});
7877

7978
if (ExtensionMode.Test === context.extensionMode && !isScenarioAutomation) {

0 commit comments

Comments
 (0)