Skip to content

Commit 492c092

Browse files
authored
Don't display chat messages quota for UBB (#313327)
1 parent 73991f0 commit 492c092

9 files changed

Lines changed: 329 additions & 5 deletions

File tree

src/vs/workbench/contrib/chat/browser/actions/chatActions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,8 @@ export function registerChatActions() {
11221122
ChatContextKeys.Entitlement.planFree,
11231123
ChatContextKeys.Entitlement.planEdu,
11241124
ChatContextKeys.Entitlement.planPro,
1125-
ChatContextKeys.Entitlement.planProPlus
1125+
ChatContextKeys.Entitlement.planProPlus,
1126+
ChatContextKeys.Entitlement.planMax
11261127
),
11271128
nonEnterpriseCopilotUsers
11281129
),

src/vs/workbench/contrib/chat/browser/chatManagement/chatManagement.contribution.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const LANGUAGE_MODELS_ENTITLEMENT_PRECONDITION = ContextKeyExpr.and(ChatContextK
3434
ChatContextKeys.Entitlement.planEdu,
3535
ChatContextKeys.Entitlement.planPro,
3636
ChatContextKeys.Entitlement.planProPlus,
37+
ChatContextKeys.Entitlement.planMax,
3738
ChatContextKeys.Entitlement.planBusiness,
3839
ChatContextKeys.Entitlement.planEnterprise,
3940
ChatContextKeys.Entitlement.internal

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
474474
ContextKeyExpr.or(
475475
ChatContextKeys.Entitlement.planPro,
476476
ChatContextKeys.Entitlement.planProPlus,
477+
ChatContextKeys.Entitlement.planMax,
477478
ChatContextKeys.Entitlement.planEdu,
478479
)
479480
),
@@ -485,6 +486,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
485486
ContextKeyExpr.or(
486487
ChatContextKeys.Entitlement.planPro,
487488
ChatContextKeys.Entitlement.planProPlus,
489+
ChatContextKeys.Entitlement.planMax,
488490
ChatContextKeys.Entitlement.planEdu,
489491
),
490492
ContextKeyExpr.or(

src/vs/workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ export class ChatStatusDashboard extends DomWidget {
150150
}));
151151

152152
// Add Additional Spend / Upgrade buttons to the header
153-
const canConfigureAdditionalSpend = this.chatEntitlementService.entitlement === ChatEntitlement.EDU || this.chatEntitlementService.entitlement === ChatEntitlement.Pro || this.chatEntitlementService.entitlement === ChatEntitlement.ProPlus;
153+
const canConfigureAdditionalSpend = this.chatEntitlementService.entitlement === ChatEntitlement.EDU || this.chatEntitlementService.entitlement === ChatEntitlement.Pro || this.chatEntitlementService.entitlement === ChatEntitlement.ProPlus || this.chatEntitlementService.entitlement === ChatEntitlement.Max;
154154
const showUpgrade = this.chatEntitlementService.entitlement !== ChatEntitlement.ProPlus &&
155+
this.chatEntitlementService.entitlement !== ChatEntitlement.Max &&
155156
this.chatEntitlementService.entitlement !== ChatEntitlement.Business &&
156157
this.chatEntitlementService.entitlement !== ChatEntitlement.Enterprise;
157158

@@ -224,7 +225,7 @@ export class ChatStatusDashboard extends DomWidget {
224225
}
225226

226227
let chatQuotaIndicator: ((quota: IQuotaSnapshot | string) => void) | undefined;
227-
if (chatQuota && !chatQuota.unlimited) {
228+
if (chatQuota && !chatQuota.unlimited && !premiumChatQuota?.usageBasedBilling) {
228229
chatQuotaIndicator = this.createQuotaIndicator(container, chatQuota, localize('chatsLabel', "Chat messages"), resetLabel);
229230
}
230231

@@ -238,7 +239,9 @@ export class ChatStatusDashboard extends DomWidget {
238239
}
239240

240241
let completionsQuotaIndicator: ((quota: IQuotaSnapshot | string) => void) | undefined;
241-
if (completionsQuota && !completionsQuota.unlimited && completionsQuota.percentRemaining >= 0) {
242+
const showCompletions = completionsQuota && !completionsQuota.unlimited && completionsQuota.percentRemaining >= 0
243+
&& (!premiumChatQuota?.usageBasedBilling || this.chatEntitlementService.entitlement === ChatEntitlement.Free);
244+
if (showCompletions) {
242245
completionsQuotaIndicator = this.createQuotaIndicator(container, completionsQuota, localize('completionsLabel', "Inline Suggestions"), resetLabel);
243246
}
244247

src/vs/workbench/contrib/chat/browser/widget/chatContentParts/chatQuotaExceededPart.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export class ChatQuotaExceededPart extends Disposable implements IChatContentPar
6767
case ChatEntitlement.EDU:
6868
case ChatEntitlement.Pro:
6969
case ChatEntitlement.ProPlus:
70+
case ChatEntitlement.Max:
7071
primaryButtonLabel = localize('enableAdditionalUsage', "Configure Additional Spend");
7172
break;
7273
case ChatEntitlement.Free:

src/vs/workbench/contrib/chat/browser/widget/input/chatModelPicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ function shouldShowManageModelsAction(chatEntitlementService: IChatEntitlementSe
174174
chatEntitlementService.entitlement === ChatEntitlement.EDU ||
175175
chatEntitlementService.entitlement === ChatEntitlement.Pro ||
176176
chatEntitlementService.entitlement === ChatEntitlement.ProPlus ||
177+
chatEntitlementService.entitlement === ChatEntitlement.Max ||
177178
chatEntitlementService.entitlement === ChatEntitlement.Business ||
178179
chatEntitlementService.entitlement === ChatEntitlement.Enterprise ||
179180
chatEntitlementService.isInternal;
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
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 assert from 'assert';
7+
import { mainWindow } from '../../../../../base/browser/window.js';
8+
import { CancellationToken } from '../../../../../base/common/cancellation.js';
9+
import { Event } from '../../../../../base/common/event.js';
10+
import { observableValue } from '../../../../../base/common/observable.js';
11+
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
12+
import { IInlineCompletionsService } from '../../../../../editor/browser/services/inlineCompletionsService.js';
13+
import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';
14+
import { ChatEntitlement, IChatEntitlementService } from '../../../../services/chat/common/chatEntitlementService.js';
15+
import { workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
16+
import { ChatStatusDashboard, IChatStatusDashboardOptions } from '../../../chat/browser/chatStatus/chatStatusDashboard.js';
17+
import { IChatStatusItemService } from '../../../chat/browser/chatStatus/chatStatusItemService.js';
18+
19+
interface IQuotaConfig {
20+
percentRemaining: number;
21+
unlimited: boolean;
22+
usageBasedBilling?: boolean;
23+
resetAt?: number;
24+
}
25+
26+
function createEntitlementService(opts: {
27+
chat?: IQuotaConfig;
28+
completions?: IQuotaConfig;
29+
premiumChat?: IQuotaConfig;
30+
additionalUsageEnabled?: boolean;
31+
entitlement?: ChatEntitlement;
32+
}): IChatEntitlementService {
33+
return {
34+
_serviceBrand: undefined,
35+
organisations: undefined,
36+
isInternal: false,
37+
sku: undefined,
38+
copilotTrackingId: undefined,
39+
onDidChangeQuotaExceeded: Event.None,
40+
onDidChangeQuotaRemaining: Event.None,
41+
quotas: {
42+
chat: opts.chat,
43+
completions: opts.completions,
44+
premiumChat: opts.premiumChat,
45+
additionalUsageEnabled: opts.additionalUsageEnabled,
46+
},
47+
update: (_token: CancellationToken) => Promise.resolve(),
48+
onDidChangeSentiment: Event.None,
49+
sentimentObs: observableValue({}, {}),
50+
sentiment: { completed: true },
51+
onDidChangeEntitlement: Event.None,
52+
entitlement: opts.entitlement ?? ChatEntitlement.Free,
53+
entitlementObs: observableValue({}, opts.entitlement ?? ChatEntitlement.Free),
54+
anonymous: false,
55+
onDidChangeAnonymous: Event.None,
56+
anonymousObs: observableValue({}, false),
57+
markAnonymousRateLimited: () => { },
58+
setForceHidden: () => { },
59+
previewFeaturesDisabled: false,
60+
clientByokEnabled: false,
61+
} as IChatEntitlementService;
62+
}
63+
64+
function getQuotaLabels(element: HTMLElement): string[] {
65+
const indicators = element.querySelectorAll('.quota-indicator:not(.included) .quota-title');
66+
return Array.from(indicators).map(el => el.textContent ?? '');
67+
}
68+
69+
function getIncludedLabels(element: HTMLElement): string[] {
70+
const indicators = element.querySelectorAll('.quota-indicator.included .quota-title');
71+
return Array.from(indicators).map(el => el.textContent ?? '');
72+
}
73+
74+
function getQuotaValues(element: HTMLElement): string[] {
75+
const values = element.querySelectorAll('.quota-indicator:not(.included) .quota-value');
76+
return Array.from(values).map(el => el.textContent ?? '');
77+
}
78+
79+
const dashboardOptions: IChatStatusDashboardOptions = {
80+
disableInlineSuggestionsSettings: true,
81+
disableModelSelection: true,
82+
disableProviderOptions: true,
83+
disableCompletionsSnooze: true,
84+
};
85+
86+
suite('ChatStatusDashboard', () => {
87+
const store = ensureNoDisposablesAreLeakedInTestSuite();
88+
89+
function createDashboard(entitlementService: IChatEntitlementService): ChatStatusDashboard {
90+
const instantiationService = workbenchInstantiationService(undefined, store);
91+
92+
instantiationService.stub(IChatEntitlementService, entitlementService);
93+
instantiationService.stub(IChatStatusItemService, {
94+
_serviceBrand: undefined,
95+
onDidChange: Event.None,
96+
setOrUpdateEntry: () => { },
97+
deleteEntry: () => { },
98+
getEntries: () => [],
99+
});
100+
instantiationService.stub(IInlineCompletionsService, {
101+
_serviceBrand: undefined,
102+
onDidChangeIsSnoozing: Event.None,
103+
snoozeTimeLeft: 0,
104+
snooze: () => { },
105+
setSnoozeDuration: () => { },
106+
});
107+
instantiationService.stub(IMarkdownRendererService, {
108+
_serviceBrand: undefined,
109+
});
110+
111+
const dashboard = store.add(instantiationService.createInstance(ChatStatusDashboard, dashboardOptions));
112+
113+
mainWindow.document.body.appendChild(dashboard.element);
114+
store.add({ dispose: () => dashboard.element.remove() });
115+
116+
return dashboard;
117+
}
118+
119+
// --- COPILOT FREE ---
120+
121+
test('Free — PRU: shows Chat messages and Inline Suggestions', () => {
122+
const dashboard = createDashboard(createEntitlementService({
123+
chat: { percentRemaining: 80, unlimited: false },
124+
completions: { percentRemaining: 70, unlimited: false },
125+
entitlement: ChatEntitlement.Free,
126+
}));
127+
128+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Chat messages', 'Inline Suggestions']);
129+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['20%', '30%']);
130+
});
131+
132+
test('Free — PRU exhausted: shows Chat messages and Inline Suggestions at 0%', () => {
133+
const dashboard = createDashboard(createEntitlementService({
134+
chat: { percentRemaining: 0, unlimited: false },
135+
completions: { percentRemaining: 0, unlimited: false },
136+
entitlement: ChatEntitlement.Free,
137+
}));
138+
139+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Chat messages', 'Inline Suggestions']);
140+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%', '100%']);
141+
});
142+
143+
test('Free — TBB: shows Monthly Limit and Inline Suggestions, not Chat messages', () => {
144+
const dashboard = createDashboard(createEntitlementService({
145+
chat: { percentRemaining: 80, unlimited: false },
146+
premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },
147+
completions: { percentRemaining: 70, unlimited: false },
148+
entitlement: ChatEntitlement.Free,
149+
}));
150+
151+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit', 'Inline Suggestions']);
152+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['40%', '30%']);
153+
});
154+
155+
test('Free — TBB exhausted: shows Monthly Limit and Inline Suggestions at 0%', () => {
156+
const dashboard = createDashboard(createEntitlementService({
157+
chat: { percentRemaining: 0, unlimited: false },
158+
premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },
159+
completions: { percentRemaining: 0, unlimited: false },
160+
entitlement: ChatEntitlement.Free,
161+
}));
162+
163+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit', 'Inline Suggestions']);
164+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%', '100%']);
165+
});
166+
167+
// --- COPILOT PRO (EDU/Pro) ---
168+
169+
test('EDU/Pro — PRU: shows Chat messages, Premium requests, and Inline Suggestions', () => {
170+
const dashboard = createDashboard(createEntitlementService({
171+
chat: { percentRemaining: 80, unlimited: false },
172+
premiumChat: { percentRemaining: 60, unlimited: false },
173+
completions: { percentRemaining: 90, unlimited: false },
174+
entitlement: ChatEntitlement.Pro,
175+
}));
176+
177+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Chat messages', 'Premium requests', 'Inline Suggestions']);
178+
});
179+
180+
test('EDU/Pro — TBB: shows only Monthly Limit, not Chat messages or Inline Suggestions', () => {
181+
const dashboard = createDashboard(createEntitlementService({
182+
chat: { percentRemaining: 80, unlimited: false },
183+
premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },
184+
completions: { percentRemaining: 90, unlimited: false },
185+
entitlement: ChatEntitlement.Pro,
186+
}));
187+
188+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);
189+
});
190+
191+
test('EDU/Pro — TBB exhausted (no overages): shows only Monthly Limit', () => {
192+
const dashboard = createDashboard(createEntitlementService({
193+
chat: { percentRemaining: 0, unlimited: false },
194+
premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },
195+
completions: { percentRemaining: 90, unlimited: false },
196+
additionalUsageEnabled: false,
197+
entitlement: ChatEntitlement.Pro,
198+
}));
199+
200+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);
201+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%']);
202+
});
203+
204+
test('EDU/Pro — TBB exhausted (with overages): shows only Monthly Limit', () => {
205+
const dashboard = createDashboard(createEntitlementService({
206+
chat: { percentRemaining: 0, unlimited: false },
207+
premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },
208+
completions: { percentRemaining: 90, unlimited: false },
209+
additionalUsageEnabled: true,
210+
entitlement: ChatEntitlement.Pro,
211+
}));
212+
213+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);
214+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%']);
215+
});
216+
217+
// --- COPILOT PRO+ ---
218+
219+
test('Pro+ — PRU: shows Premium requests and Inline Suggestions', () => {
220+
const dashboard = createDashboard(createEntitlementService({
221+
premiumChat: { percentRemaining: 60, unlimited: false },
222+
completions: { percentRemaining: 90, unlimited: false },
223+
entitlement: ChatEntitlement.ProPlus,
224+
}));
225+
226+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Premium requests', 'Inline Suggestions']);
227+
});
228+
229+
test('Pro+ — TBB with quota: shows only Monthly Limit', () => {
230+
const dashboard = createDashboard(createEntitlementService({
231+
chat: { percentRemaining: 80, unlimited: false },
232+
premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },
233+
completions: { percentRemaining: 90, unlimited: false },
234+
entitlement: ChatEntitlement.ProPlus,
235+
}));
236+
237+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);
238+
});
239+
240+
test('Pro+ — TBB out of quota: shows only Monthly Limit', () => {
241+
const dashboard = createDashboard(createEntitlementService({
242+
chat: { percentRemaining: 0, unlimited: false },
243+
premiumChat: { percentRemaining: 0, unlimited: false, usageBasedBilling: true },
244+
completions: { percentRemaining: 90, unlimited: false },
245+
entitlement: ChatEntitlement.ProPlus,
246+
}));
247+
248+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);
249+
assert.deepStrictEqual(getQuotaValues(dashboard.element), ['100%']);
250+
});
251+
252+
// --- COPILOT MAX ---
253+
254+
test('Max Yearly — no TBB: shows unlimited Premium Requests included indicator', () => {
255+
const dashboard = createDashboard(createEntitlementService({
256+
premiumChat: { percentRemaining: 100, unlimited: true },
257+
completions: { percentRemaining: 100, unlimited: true },
258+
entitlement: ChatEntitlement.Max,
259+
}));
260+
261+
// Unlimited quotas are not shown as quota indicators
262+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), []);
263+
// Instead shown as "included" indicator
264+
assert.deepStrictEqual(getIncludedLabels(dashboard.element), ['Premium Requests']);
265+
});
266+
267+
test('Max Monthly — TBB: shows unlimited Monthly Limit included indicator', () => {
268+
const dashboard = createDashboard(createEntitlementService({
269+
premiumChat: { percentRemaining: 100, unlimited: true, usageBasedBilling: true },
270+
completions: { percentRemaining: 100, unlimited: true },
271+
entitlement: ChatEntitlement.Max,
272+
}));
273+
274+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), []);
275+
assert.deepStrictEqual(getIncludedLabels(dashboard.element), ['Monthly Limit']);
276+
});
277+
278+
// --- BUSINESS / ENTERPRISE ---
279+
280+
test('Enterprise Managed — PRU: shows Premium requests with unlimited included', () => {
281+
const dashboard = createDashboard(createEntitlementService({
282+
premiumChat: { percentRemaining: 100, unlimited: true },
283+
completions: { percentRemaining: 100, unlimited: true },
284+
entitlement: ChatEntitlement.Business,
285+
}));
286+
287+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), []);
288+
assert.deepStrictEqual(getIncludedLabels(dashboard.element), ['Premium Requests']);
289+
});
290+
291+
test('Enterprise — TBB (multi-quota): shows only Monthly Limit, not Chat messages or Inline Suggestions', () => {
292+
const dashboard = createDashboard(createEntitlementService({
293+
chat: { percentRemaining: 80, unlimited: false },
294+
premiumChat: { percentRemaining: 60, unlimited: false, usageBasedBilling: true },
295+
completions: { percentRemaining: 70, unlimited: false },
296+
entitlement: ChatEntitlement.Enterprise,
297+
}));
298+
299+
assert.deepStrictEqual(getQuotaLabels(dashboard.element), ['Monthly Limit']);
300+
});
301+
});

src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [
263263
steps: [
264264
createCopilotSetupStep('CopilotSetupAnonymous', CopilotAnonymousButton, 'chatAnonymous && !chatSetupCompleted', true),
265265
createCopilotSetupStep('CopilotSetupSignedOut', CopilotSignedOutButton, 'chatEntitlementSignedOut && !chatAnonymous', false),
266-
createCopilotSetupStep('CopilotSetupComplete', CopilotCompleteButton, 'chatSetupCompleted && !chatSetupDisabled && (chatAnonymous || chatPlanPro || chatPlanProPlus || chatPlanBusiness || chatPlanEnterprise || chatPlanFree)', false),
266+
createCopilotSetupStep('CopilotSetupComplete', CopilotCompleteButton, 'chatSetupCompleted && !chatSetupDisabled && (chatAnonymous || chatPlanPro || chatPlanProPlus || chatPlanMax || chatPlanBusiness || chatPlanEnterprise || chatPlanFree)', false),
267267
createCopilotSetupStep('CopilotSetupSignedIn', CopilotSignedInButton, '!chatEntitlementSignedOut && (!chatSetupCompleted || chatSetupDisabled || chatPlanCanSignUp)', false),
268268
{
269269
id: 'pickColorTheme',

0 commit comments

Comments
 (0)