Skip to content

Commit 5e41ad0

Browse files
authored
chat Copilot Chat appears signed out in status bar but works in Chat (fix #253652) (#260597)
* chat Copilot Chat appears signed out in status bar but works in Chat (fix #253652) * fix - return all matching sessions in `ChatEntitlementRequests`
1 parent cd13a25 commit 5e41ad0

File tree

2 files changed

+67
-44
lines changed

2 files changed

+67
-44
lines changed

src/vs/workbench/contrib/chat/browser/chatSetup.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,27 +1306,29 @@ class ChatSetupController extends Disposable {
13061306
let signUpResult: boolean | { errorCode: number } | undefined = undefined;
13071307

13081308
const provider = options.useSocialProvider ?? options.useEnterpriseProvider ? defaultChat.provider.enterprise.id : defaultChat.provider.default.id;
1309-
1309+
let sessions = session ? [session] : undefined;
13101310
try {
13111311
if (
13121312
entitlement !== ChatEntitlement.Free && // User is not signed up to Copilot Free
13131313
!isProUser(entitlement) && // User is not signed up for a Copilot subscription
13141314
entitlement !== ChatEntitlement.Unavailable // User is eligible for Copilot Free
13151315
) {
1316-
if (!session) {
1316+
if (!sessions) {
13171317
try {
1318-
session = (await this.authenticationService.getSessions(providerId)).at(0);
1318+
// Consider all sessions for the provider to be suitable for signing up
1319+
const existingSessions = await this.authenticationService.getSessions(providerId);
1320+
sessions = existingSessions.length > 0 ? [...existingSessions] : undefined;
13191321
} catch (error) {
13201322
// ignore - errors can throw if a provider is not registered
13211323
}
13221324

1323-
if (!session) {
1325+
if (!sessions || sessions.length === 0) {
13241326
this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.chatInstall', { installResult: 'failedNoSession', installDuration: watch.elapsed(), signUpErrorCode: undefined, provider });
13251327
return false; // unexpected
13261328
}
13271329
}
13281330

1329-
signUpResult = await this.requests.signUpFree(session);
1331+
signUpResult = await this.requests.signUpFree(sessions);
13301332

13311333
if (typeof signUpResult !== 'boolean' /* error */) {
13321334
this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.chatInstall', { installResult: 'failedSignUp', installDuration: watch.elapsed(), signUpErrorCode: signUpResult.errorCode, provider });

src/vs/workbench/contrib/chat/common/chatEntitlementService.ts

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -515,21 +515,26 @@ export class ChatEntitlementRequests extends Disposable {
515515
}
516516
}
517517

518-
private async findMatchingProviderSession(token: CancellationToken): Promise<AuthenticationSession | undefined> {
518+
private async findMatchingProviderSession(token: CancellationToken): Promise<AuthenticationSession[] | undefined> {
519519
const sessions = await this.doGetSessions(ChatEntitlementRequests.providerId(this.configurationService));
520520
if (token.isCancellationRequested) {
521521
return undefined;
522522
}
523523

524+
const matchingSessions = new Set<AuthenticationSession>();
524525
for (const session of sessions) {
525526
for (const scopes of defaultChat.providerScopes) {
526-
if (this.scopesMatch(session.scopes, scopes)) {
527-
return session;
527+
if (this.includesScopes(session.scopes, scopes)) {
528+
matchingSessions.add(session);
528529
}
529530
}
530531
}
531532

532-
return undefined;
533+
// We intentionally want to return an array of matching sessions and
534+
// not just the first, because it is possible that a matching session
535+
// has an expired token. As such, we want to try them all until we
536+
// succeeded with the request.
537+
return matchingSessions.size > 0 ? Array.from(matchingSessions) : undefined;
533538
}
534539

535540
private async doGetSessions(providerId: string): Promise<readonly AuthenticationSession[]> {
@@ -551,12 +556,12 @@ export class ChatEntitlementRequests extends Disposable {
551556
return [];
552557
}
553558

554-
private scopesMatch(scopes: ReadonlyArray<string>, expectedScopes: string[]): boolean {
555-
return scopes.length === expectedScopes.length && expectedScopes.every(scope => scopes.includes(scope));
559+
private includesScopes(scopes: ReadonlyArray<string>, expectedScopes: string[]): boolean {
560+
return expectedScopes.every(scope => scopes.includes(scope));
556561
}
557562

558-
private async resolveEntitlement(session: AuthenticationSession, token: CancellationToken): Promise<IEntitlements | undefined> {
559-
const entitlements = await this.doResolveEntitlement(session, token);
563+
private async resolveEntitlement(sessions: AuthenticationSession[], token: CancellationToken): Promise<IEntitlements | undefined> {
564+
const entitlements = await this.doResolveEntitlement(sessions, token);
560565
if (typeof entitlements?.entitlement === 'number' && !token.isCancellationRequested) {
561566
this.didResolveEntitlements = true;
562567
this.update(entitlements);
@@ -565,7 +570,7 @@ export class ChatEntitlementRequests extends Disposable {
565570
return entitlements;
566571
}
567572

568-
private async doResolveEntitlement(session: AuthenticationSession, token: CancellationToken): Promise<IEntitlements | undefined> {
573+
private async doResolveEntitlement(sessions: AuthenticationSession[], token: CancellationToken): Promise<IEntitlements | undefined> {
569574
if (ChatEntitlementRequests.providerId(this.configurationService) === defaultChat.provider.enterprise.id) {
570575
this.logService.trace('[chat entitlement]: enterprise provider, assuming Enterprise plan');
571576
return { entitlement: ChatEntitlement.Enterprise };
@@ -575,7 +580,7 @@ export class ChatEntitlementRequests extends Disposable {
575580
return undefined;
576581
}
577582

578-
const response = await this.request(defaultChat.entitlementUrl, 'GET', undefined, session, token);
583+
const response = await this.request(defaultChat.entitlementUrl, 'GET', undefined, sessions, token);
579584
if (token.isCancellationRequested) {
580585
return undefined;
581586
}
@@ -713,26 +718,42 @@ export class ChatEntitlementRequests extends Disposable {
713718
return quotas;
714719
}
715720

716-
private async request(url: string, type: 'GET', body: undefined, session: AuthenticationSession, token: CancellationToken): Promise<IRequestContext | undefined>;
717-
private async request(url: string, type: 'POST', body: object, session: AuthenticationSession, token: CancellationToken): Promise<IRequestContext | undefined>;
718-
private async request(url: string, type: 'GET' | 'POST', body: object | undefined, session: AuthenticationSession, token: CancellationToken): Promise<IRequestContext | undefined> {
719-
try {
720-
return await this.requestService.request({
721-
type,
722-
url,
723-
data: type === 'POST' ? JSON.stringify(body) : undefined,
724-
disableCache: true,
725-
headers: {
726-
'Authorization': `Bearer ${session.accessToken}`
727-
}
728-
}, token);
729-
} catch (error) {
730-
if (!token.isCancellationRequested) {
731-
this.logService.error(`[chat entitlement] request: error ${error}`);
721+
private async request(url: string, type: 'GET', body: undefined, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined>;
722+
private async request(url: string, type: 'POST', body: object, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined>;
723+
private async request(url: string, type: 'GET' | 'POST', body: object | undefined, sessions: AuthenticationSession[], token: CancellationToken): Promise<IRequestContext | undefined> {
724+
let lastRequest: IRequestContext | undefined;
725+
726+
for (const session of sessions) {
727+
if (token.isCancellationRequested) {
728+
return lastRequest;
732729
}
733730

734-
return undefined;
731+
try {
732+
const response = await this.requestService.request({
733+
type,
734+
url,
735+
data: type === 'POST' ? JSON.stringify(body) : undefined,
736+
disableCache: true,
737+
headers: {
738+
'Authorization': `Bearer ${session.accessToken}`
739+
}
740+
}, token);
741+
742+
const status = response.res.statusCode;
743+
if (status && status !== 200) {
744+
lastRequest = response;
745+
continue; // try next session
746+
}
747+
748+
return response;
749+
} catch (error) {
750+
if (!token.isCancellationRequested) {
751+
this.logService.error(`[chat entitlement] request: error ${error}`);
752+
}
753+
}
735754
}
755+
756+
return lastRequest;
736757
}
737758

738759
private update(state: IEntitlements): void {
@@ -745,28 +766,28 @@ export class ChatEntitlementRequests extends Disposable {
745766
}
746767
}
747768

748-
async forceResolveEntitlement(session: AuthenticationSession | undefined, token = CancellationToken.None): Promise<IEntitlements | undefined> {
749-
if (!session) {
750-
session = await this.findMatchingProviderSession(token);
769+
async forceResolveEntitlement(sessions: AuthenticationSession[] | undefined, token = CancellationToken.None): Promise<IEntitlements | undefined> {
770+
if (!sessions) {
771+
sessions = await this.findMatchingProviderSession(token);
751772
}
752773

753-
if (!session) {
774+
if (!sessions || sessions.length === 0) {
754775
return undefined;
755776
}
756777

757-
return this.resolveEntitlement(session, token);
778+
return this.resolveEntitlement(sessions, token);
758779
}
759780

760-
async signUpFree(session: AuthenticationSession): Promise<true /* signed up */ | false /* already signed up */ | { errorCode: number } /* error */> {
781+
async signUpFree(sessions: AuthenticationSession[]): Promise<true /* signed up */ | false /* already signed up */ | { errorCode: number } /* error */> {
761782
const body = {
762783
restricted_telemetry: this.telemetryService.telemetryLevel === TelemetryLevel.NONE ? 'disabled' : 'enabled',
763784
public_code_suggestions: 'enabled'
764785
};
765786

766-
const response = await this.request(defaultChat.entitlementSignupLimitedUrl, 'POST', body, session, CancellationToken.None);
787+
const response = await this.request(defaultChat.entitlementSignupLimitedUrl, 'POST', body, sessions, CancellationToken.None);
767788
if (!response) {
768789
const retry = await this.onUnknownSignUpError(localize('signUpNoResponseError', "No response received."), '[chat entitlement] sign-up: no response');
769-
return retry ? this.signUpFree(session) : { errorCode: 1 };
790+
return retry ? this.signUpFree(sessions) : { errorCode: 1 };
770791
}
771792

772793
if (response.res.statusCode && response.res.statusCode !== 200) {
@@ -785,7 +806,7 @@ export class ChatEntitlementRequests extends Disposable {
785806
}
786807
}
787808
const retry = await this.onUnknownSignUpError(localize('signUpUnexpectedStatusError', "Unexpected status code {0}.", response.res.statusCode), `[chat entitlement] sign-up: unexpected status code ${response.res.statusCode}`);
788-
return retry ? this.signUpFree(session) : { errorCode: response.res.statusCode };
809+
return retry ? this.signUpFree(sessions) : { errorCode: response.res.statusCode };
789810
}
790811

791812
let responseText: string | null = null;
@@ -797,7 +818,7 @@ export class ChatEntitlementRequests extends Disposable {
797818

798819
if (!responseText) {
799820
const retry = await this.onUnknownSignUpError(localize('signUpNoResponseContentsError', "Response has no contents."), '[chat entitlement] sign-up: response has no content');
800-
return retry ? this.signUpFree(session) : { errorCode: 2 };
821+
return retry ? this.signUpFree(sessions) : { errorCode: 2 };
801822
}
802823

803824
let parsedResult: { subscribed: boolean } | undefined = undefined;
@@ -806,7 +827,7 @@ export class ChatEntitlementRequests extends Disposable {
806827
this.logService.trace(`[chat entitlement] sign-up: response is ${responseText}`);
807828
} catch (err) {
808829
const retry = await this.onUnknownSignUpError(localize('signUpInvalidResponseError', "Invalid response contents."), `[chat entitlement] sign-up: error parsing response (${err})`);
809-
return retry ? this.signUpFree(session) : { errorCode: 3 };
830+
return retry ? this.signUpFree(sessions) : { errorCode: 3 };
810831
}
811832

812833
// We have made it this far, so the user either did sign-up or was signed-up already.
@@ -862,7 +883,7 @@ export class ChatEntitlementRequests extends Disposable {
862883
this.authenticationExtensionsService.updateAccountPreference(defaultChat.extensionId, providerId, session.account);
863884
this.authenticationExtensionsService.updateAccountPreference(defaultChat.chatExtensionId, providerId, session.account);
864885

865-
const entitlements = await this.forceResolveEntitlement(session);
886+
const entitlements = await this.forceResolveEntitlement([session]);
866887

867888
return { session, entitlements };
868889
}

0 commit comments

Comments
 (0)