Skip to content

Commit 23e2fba

Browse files
authored
chat - make status a control with lifecycle and adopt more services (microsoft#242376)
1 parent 0ae8e42 commit 23e2fba

File tree

8 files changed

+227
-214
lines changed

8 files changed

+227
-214
lines changed

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { IActionViewItemService } from '../../../../../platform/actions/browser/
2121
import { DropdownWithPrimaryActionViewItem } from '../../../../../platform/actions/browser/dropdownWithPrimaryActionViewItem.js';
2222
import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
2323
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
24-
import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
24+
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
2525
import { IsLinuxContext, IsWindowsContext } from '../../../../../platform/contextkey/common/contextkeys.js';
2626
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
2727
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
@@ -55,6 +55,7 @@ import { language } from '../../../../../base/common/platform.js';
5555
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
5656
import { IWorkbenchLayoutService, Parts } from '../../../../services/layout/browser/layoutService.js';
5757
import { IViewDescriptorService, ViewContainerLocation } from '../../../../common/views.js';
58+
import { ChatEntitlement, IChatEntitlementsService } from '../../common/chatEntitlementsService.js';
5859

5960
export const CHAT_CATEGORY = localize2('chat.category', 'Chat');
6061

@@ -694,12 +695,10 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben
694695
@IChatAgentService agentService: IChatAgentService,
695696
@IChatQuotasService chatQuotasService: IChatQuotasService,
696697
@IInstantiationService instantiationService: IInstantiationService,
697-
@IContextKeyService contextKeyService: IContextKeyService,
698+
@IChatEntitlementsService chatEntitlementsService: IChatEntitlementsService
698699
) {
699700
super();
700701

701-
const contextKeySet = new Set([ChatContextKeys.Setup.signedOut.key]);
702-
703702
const disposable = actionViewItemService.register(MenuId.CommandCenter, MenuId.ChatTitleBarMenu, (action, options) => {
704703
if (!(action instanceof SubmenuItemAction)) {
705704
return undefined;
@@ -713,7 +712,7 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben
713712

714713
const chatExtensionInstalled = agentService.getAgents().some(agent => agent.isDefault);
715714
const { chatQuotaExceeded, completionsQuotaExceeded } = chatQuotasService.quotas;
716-
const signedOut = contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.signedOut.key) ?? false;
715+
const signedOut = chatEntitlementsService.entitlement === ChatEntitlement.Unknown;
717716

718717
let primaryActionId: string;
719718
let primaryActionTitle: string;
@@ -745,7 +744,7 @@ export class CopilotTitleBarMenuRendering extends Disposable implements IWorkben
745744
}, Event.any(
746745
agentService.onDidChangeAgents,
747746
chatQuotasService.onDidChangeQuotaExceeded,
748-
Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(contextKeySet))
747+
chatEntitlementsService.onDidChangeEntitlement
749748
));
750749

751750
// Reduces flicker a bit on reload/restart

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

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ import { QuickChatService } from './chatQuick.js';
8181
import { ChatQuotasService, IChatQuotasService } from '../common/chatQuotasService.js';
8282
import { ChatResponseAccessibleView } from './chatResponseAccessibleView.js';
8383
import { ChatEntitlementsService, ChatSetupContribution } from './chatSetup.js';
84-
import { IChatEntitlementsService } from '../common/chatEntitlementsService.js';
84+
import { ChatEntitlement, IChatEntitlementsService } from '../common/chatEntitlementsService.js';
8585
import { ChatVariablesService } from './chatVariables.js';
8686
import { ChatWidgetService } from './chatWidget.js';
8787
import { ChatCodeBlockContextProviderService } from './codeBlockContextProviderService.js';
@@ -100,6 +100,7 @@ import { registerChatToolActions } from './actions/chatToolActions.js';
100100
import { ChatStatusBarEntry } from './chatStatus.js';
101101
import product from '../../../../platform/product/common/product.js';
102102
import { ChatEditingNotebookFileSystemProviderContrib } from './chatEditing/chatEditingNotebookFileSystemProvider.js';
103+
import { Event } from '../../../../base/common/event.js';
103104

104105
// Register configuration
105106
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
@@ -299,34 +300,39 @@ class ChatResolverContribution extends Disposable {
299300
}
300301
}
301302

302-
class ChatAgentSettingContribution implements IWorkbenchContribution {
303+
class ChatAgentSettingContribution extends Disposable implements IWorkbenchContribution {
303304

304305
static readonly ID = 'workbench.contrib.chatAgentSetting';
305306

306307
private registeredNode: IConfigurationNode | undefined;
307308

308309
constructor(
309-
@IWorkbenchAssignmentService experimentService: IWorkbenchAssignmentService,
310+
@IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService,
310311
@IProductService private readonly productService: IProductService,
311312
@IContextKeyService contextKeyService: IContextKeyService,
313+
@IChatEntitlementsService private readonly entitlementService: IChatEntitlementsService,
312314
) {
315+
super();
316+
313317
if (this.productService.quality !== 'stable') {
314-
this.registerSetting();
318+
this.registerEnablementSetting();
315319
}
316320

317321
const expDisabledKey = ChatContextKeys.Editing.agentModeDisallowed.bindTo(contextKeyService);
318322
experimentService.getTreatment<boolean>('chatAgentEnabled').then(enabled => {
319323
if (enabled) {
320-
this.registerSetting();
324+
this.registerEnablementSetting();
321325
expDisabledKey.set(false);
322326
} else if (enabled === false) {
323327
this.deregisterSetting();
324328
expDisabledKey.set(true);
325329
}
326330
});
331+
332+
this.registerMaxRequestsSetting();
327333
}
328334

329-
private registerSetting() {
335+
private registerEnablementSetting() {
330336
if (this.registeredNode) {
331337
return;
332338
}
@@ -353,6 +359,34 @@ class ChatAgentSettingContribution implements IWorkbenchContribution {
353359
this.registeredNode = undefined;
354360
}
355361
}
362+
363+
private registerMaxRequestsSetting(): void {
364+
let lastNode: IConfigurationNode | undefined;
365+
const registerMaxRequestsSetting = () => {
366+
const treatmentId = this.entitlementService.entitlement === ChatEntitlement.Limited ?
367+
'chatAgentMaxRequestsFree' :
368+
'chatAgentMaxRequestsPro';
369+
this.experimentService.getTreatment<number>(treatmentId).then(value => {
370+
const defaultValue = value ?? (this.entitlementService.entitlement === ChatEntitlement.Limited ? 5 : 15);
371+
const node: IConfigurationNode = {
372+
id: 'chatSidebar',
373+
title: nls.localize('interactiveSessionConfigurationTitle', "Chat"),
374+
type: 'object',
375+
properties: {
376+
'chat.agent.maxRequests': {
377+
type: 'number',
378+
markdownDescription: nls.localize('chat.agent.maxRequests', "The maximum number of requests to allow Copilot Edits to use per-turn in agent mode. When the limit is reached, Copilot will ask the user to confirm that it should keep working. \n\n> **Note**: For users on the Copilot Free plan, note that each agent mode request currently uses one chat request."),
379+
default: defaultValue,
380+
tags: ['experimental']
381+
},
382+
}
383+
};
384+
configurationRegistry.updateConfigurations({ remove: lastNode ? [lastNode] : [], add: [node] });
385+
lastNode = node;
386+
});
387+
};
388+
this._register(Event.runAndSubscribe(Event.debounce(this.entitlementService.onDidChangeEntitlement, () => { }, 1000), () => registerMaxRequestsSetting()));
389+
}
356390
}
357391

358392
AccessibleViewRegistry.register(new ChatResponseAccessibleView());

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedNotebookDiff.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
7-
import { computeDiff } from '../../../notebook/browser/diff/notebookDiffViewModel.js';
7+
import { computeDiff } from '../../../notebook/common/notebookDiff.js';
88
import { INotebookEditorModelResolverService } from '../../../notebook/common/notebookEditorModelResolverService.js';
99
import { INotebookLoggingService } from '../../../notebook/common/notebookLoggingService.js';
1010
import { INotebookEditorWorkerService } from '../../../notebook/common/services/notebookWorkerService.js';

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import { revealInSideBarCommand } from '../../files/browser/fileActions.contribu
7777
import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js';
7878
import { ChatContextKeys } from '../common/chatContextKeys.js';
7979
import { IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../common/chatEditingService.js';
80+
import { ChatEntitlement, IChatEntitlementsService } from '../common/chatEntitlementsService.js';
8081
import { IChatRequestVariableEntry, isImageVariableEntry, isLinkVariableEntry, isPasteVariableEntry } from '../common/chatModel.js';
8182
import { IChatFollowup } from '../common/chatService.js';
8283
import { IChatVariablesService } from '../common/chatVariables.js';
@@ -1618,6 +1619,7 @@ class ModelPickerActionViewItem extends DropdownMenuActionViewItemWithKeybinding
16181619
@IContextMenuService contextMenuService: IContextMenuService,
16191620
@IKeybindingService keybindingService: IKeybindingService,
16201621
@IContextKeyService contextKeyService: IContextKeyService,
1622+
@IChatEntitlementsService chatEntitlementsService: IChatEntitlementsService,
16211623
@ICommandService commandService: ICommandService,
16221624
) {
16231625
const modelActionsProvider: IActionProvider = {
@@ -1640,7 +1642,7 @@ class ModelPickerActionViewItem extends DropdownMenuActionViewItemWithKeybinding
16401642

16411643
const models: ILanguageModelChatMetadataAndIdentifier[] = this.delegate.getModels();
16421644
const actions = models.map(entry => setLanguageModelAction(entry));
1643-
if (contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.limited.key) === true) {
1645+
if (chatEntitlementsService.entitlement === ChatEntitlement.Limited) {
16441646
actions.push(new Separator());
16451647
actions.push(toAction({ id: 'moreModels', label: localize('chat.moreModels', "Add More Models..."), run: () => commandService.executeCommand('workbench.action.chat.upgradePlan') }));
16461648
}

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

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import { CHAT_CATEGORY, CHAT_SETUP_ACTION_ID, CHAT_SETUP_ACTION_LABEL } from './
5252
import { ChatViewId, EditsViewId, ensureSideBarChatViewSize, preferCopilotEditsView, showCopilotView } from './chat.js';
5353
import { CHAT_EDITING_SIDEBAR_PANEL_ID, CHAT_SIDEBAR_PANEL_ID } from './chatViewPane.js';
5454
import { ChatViewsWelcomeExtensions, IChatViewsWelcomeContributionRegistry } from './viewsWelcome/chatViewsWelcome.js';
55-
import { IChatQuotasService } from '../common/chatQuotasService.js';
55+
import { ChatQuotasService, IChatQuotasService } from '../common/chatQuotasService.js';
5656
import { mainWindow } from '../../../../base/browser/window.js';
5757
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
5858
import { URI } from '../../../../base/common/uri.js';
@@ -64,11 +64,10 @@ import { ExtensionUrlHandlerOverrideRegistry } from '../../../services/extension
6464
import { IWorkspaceTrustRequestService } from '../../../../platform/workspace/common/workspaceTrust.js';
6565
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
6666
import { StopWatch } from '../../../../base/common/stopwatch.js';
67-
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationNode } from '../../../../platform/configuration/common/configurationRegistry.js';
67+
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js';
6868
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
6969
import { ILifecycleService } from '../../../services/lifecycle/common/lifecycle.js';
7070
import { equalsIgnoreCase } from '../../../../base/common/strings.js';
71-
import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js';
7271
import { ChatEntitlement, IChatEntitlements, IChatEntitlementsService } from '../common/chatEntitlementsService.js';
7372
import { IStatusbarService } from '../../../services/statusbar/browser/statusbar.js';
7473

@@ -99,13 +98,39 @@ export class ChatEntitlementsService extends Disposable implements IChatEntitlem
9998

10099
declare _serviceBrand: undefined;
101100

101+
readonly onDidChangeEntitlement = Event.map(
102+
Event.filter(
103+
this.contextKeyService.onDidChangeContext, e => e.affectsSome(new Set([
104+
ChatContextKeys.Setup.pro.key,
105+
ChatContextKeys.Setup.limited.key,
106+
ChatContextKeys.Setup.canSignUp.key,
107+
ChatContextKeys.Setup.signedOut.key
108+
])), this._store
109+
), () => { }, this._store
110+
);
111+
102112
readonly context: Lazy<ChatSetupContext> | undefined;
103113
readonly requests: Lazy<ChatSetupRequests> | undefined;
104114

115+
get entitlement(): ChatEntitlement {
116+
if (this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.pro.key) === true) {
117+
return ChatEntitlement.Pro;
118+
} else if (this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.limited.key) === true) {
119+
return ChatEntitlement.Limited;
120+
} else if (this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.canSignUp.key) === true) {
121+
return ChatEntitlement.Available;
122+
} else if (this.contextKeyService.getContextKeyValue<boolean>(ChatContextKeys.Setup.signedOut.key) === true) {
123+
return ChatEntitlement.Unknown;
124+
}
125+
126+
return ChatEntitlement.Unresolved;
127+
}
128+
105129
constructor(
106130
@IInstantiationService instantiationService: IInstantiationService,
107131
@IProductService productService: IProductService,
108-
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
132+
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
133+
@IContextKeyService private readonly contextKeyService: IContextKeyService,
109134
) {
110135
super();
111136

@@ -138,8 +163,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
138163
@IInstantiationService private readonly instantiationService: IInstantiationService,
139164
@ICommandService private readonly commandService: ICommandService,
140165
@ITelemetryService private readonly telemetryService: ITelemetryService,
141-
@IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService,
142-
@IChatEntitlementsService chatEntitlementsService: ChatEntitlementsService,
166+
@IChatEntitlementsService chatEntitlementsService: ChatEntitlementsService
143167
) {
144168
super();
145169

@@ -154,7 +178,6 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
154178
this.registerChatWelcome(controller, context);
155179
this.registerActions(context, requests);
156180
this.registerUrlLinkHandler();
157-
this.registerSetting(context);
158181
}
159182

160183
private registerChatWelcome(controller: Lazy<ChatSetupController>, context: ChatSetupContext): void {
@@ -335,36 +358,6 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr
335358
}
336359
}));
337360
}
338-
339-
private registerSetting(context: ChatSetupContext): void {
340-
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
341-
342-
let lastNode: IConfigurationNode | undefined;
343-
const registerSetting = () => {
344-
const treatmentId = context.state.entitlement === ChatEntitlement.Limited ?
345-
'chatAgentMaxRequestsFree' :
346-
'chatAgentMaxRequestsPro';
347-
this.experimentService.getTreatment<number>(treatmentId).then(value => {
348-
const defaultValue = value ?? (context.state.entitlement === ChatEntitlement.Limited ? 5 : 15);
349-
const node: IConfigurationNode = {
350-
id: 'chatSidebar',
351-
title: localize('interactiveSessionConfigurationTitle', "Chat"),
352-
type: 'object',
353-
properties: {
354-
'chat.agent.maxRequests': {
355-
type: 'number',
356-
markdownDescription: localize('chat.agent.maxRequests', "The maximum number of requests to allow Copilot Edits to use per-turn in agent mode. When the limit is reached, Copilot will ask the user to confirm that it should keep working. \n\n> **Note**: For users on the Copilot Free plan, note that each agent mode request currently uses one chat request."),
357-
default: defaultValue,
358-
tags: ['experimental']
359-
},
360-
}
361-
};
362-
configurationRegistry.updateConfigurations({ remove: lastNode ? [lastNode] : [], add: [node] });
363-
lastNode = node;
364-
});
365-
};
366-
this._register(Event.runAndSubscribe(Event.debounce(context.onDidChange, () => { }, 1000), () => registerSetting()));
367-
}
368361
}
369362

370363
//#endregion
@@ -427,7 +420,7 @@ class ChatSetupRequests extends Disposable {
427420
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
428421
@ILogService private readonly logService: ILogService,
429422
@IRequestService private readonly requestService: IRequestService,
430-
@IChatQuotasService private readonly chatQuotasService: IChatQuotasService,
423+
@IChatQuotasService private readonly chatQuotasService: ChatQuotasService,
431424
@IDialogService private readonly dialogService: IDialogService,
432425
@IOpenerService private readonly openerService: IOpenerService,
433426
@IConfigurationService private readonly configurationService: IConfigurationService
@@ -602,13 +595,16 @@ class ChatSetupRequests extends Disposable {
602595
entitlement = ChatEntitlement.Unavailable;
603596
}
604597

598+
const chatRemaining = entitlementsResponse.limited_user_quotas?.chat;
599+
const completionsRemaining = entitlementsResponse.limited_user_quotas?.completions;
600+
605601
const entitlements: IChatEntitlements = {
606602
entitlement,
607603
quotas: {
608604
chatTotal: entitlementsResponse.monthly_quotas?.chat,
609605
completionsTotal: entitlementsResponse.monthly_quotas?.completions,
610-
chatRemaining: entitlementsResponse.limited_user_quotas?.chat,
611-
completionsRemaining: entitlementsResponse.limited_user_quotas?.completions,
606+
chatRemaining: typeof chatRemaining === 'number' ? Math.max(0, chatRemaining) : undefined,
607+
completionsRemaining: typeof completionsRemaining === 'number' ? Math.max(0, completionsRemaining) : undefined,
612608
resetDate: entitlementsResponse.limited_user_reset_date
613609
}
614610
};

0 commit comments

Comments
 (0)