44package com .microsoft .copilot .eclipse .ui .handlers ;
55
66import java .lang .reflect .Field ;
7+ import java .time .LocalDate ;
8+ import java .time .format .DateTimeFormatter ;
79import java .util .ArrayList ;
810import java .util .List ;
911import java .util .Map ;
@@ -149,6 +151,22 @@ private void addCopilotUsageItems(AuthStatusManager authStatusManager, List<ICon
149151 // TODO: remove reset date null check when the CLS is ready for all IDEs.
150152 items .add (new Separator ());
151153
154+ if (quotaStatus .tokenBasedBillingEnabled ()) {
155+ addCopilotUsageItemsTbb (items , quotaStatus );
156+ } else {
157+ // TODO: Remove this legacy branch after TBB is officially released.
158+ addCopilotUsageItemsLegacy (items , quotaStatus );
159+ }
160+
161+ // Create a CompletableFuture to update quota information
162+ CopilotCore .getPlugin ().getAuthStatusManager ().checkQuota ().thenAccept (this ::updateQuotaItems );
163+ }
164+
165+ /**
166+ * Renders the Copilot usage rows using the token-based-billing layout (Monthly Limit / Included
167+ * Credits, Enable Additional Usage / Increase Budget, Upgrade Plan, dynamic allowance reset).
168+ */
169+ private void addCopilotUsageItemsTbb (List <IContributionItem > items , CheckQuotaResult quotaStatus ) {
152170 ImageDescriptor usageIcon = MenuUtils .getUsageIcon (MenuUtils .calculatePercentRemaining (quotaStatus ));
153171 ImageDescriptor blankIcon = MenuUtils .getBlankIcon ();
154172 CopilotPlan plan = quotaStatus .copilotPlan ();
@@ -213,8 +231,94 @@ private void addCopilotUsageItems(AuthStatusManager authStatusManager, List<ICon
213231 items .add (createCommandItem ("com.microsoft.copilot.eclipse.commands.upgradeCopilotPlan" ,
214232 Messages .menu_quota_upgradePlan , upgradePlanIcon ));
215233 }
216- // Create a CompletableFuture to update quota information
217- CopilotCore .getPlugin ().getAuthStatusManager ().checkQuota ().thenAccept (this ::updateQuotaItems );
234+ }
235+
236+ // TODO: Remove this legacy fallback after TBB is officially released.
237+ /**
238+ * Renders the original main-branch Copilot usage rows. Used when the language server has not
239+ * enabled token-based billing yet, in which case the new TBB-only APIs (premium interactions
240+ * entitlement, overage budget UI, dynamic allowance-reset wording) are not relied on.
241+ */
242+ private void addCopilotUsageItemsLegacy (List <IContributionItem > items , CheckQuotaResult quotaStatus ) {
243+ Quota completionsQuota = quotaStatus .completions ();
244+ Quota chatQuota = quotaStatus .chat ();
245+ Quota premiumQuota = quotaStatus .premiumInteractions ();
246+ CopilotPlan plan = quotaStatus .copilotPlan ();
247+
248+ // Calculate percentRemaining based on plan
249+ double percentRemaining ;
250+ if (plan == CopilotPlan .free ) {
251+ percentRemaining = Math .min (completionsQuota .percentRemaining (), chatQuota .percentRemaining ());
252+ } else if (premiumQuota != null ) {
253+ percentRemaining = Math .min (completionsQuota .percentRemaining (),
254+ Math .min (chatQuota .percentRemaining (), premiumQuota .percentRemaining ()));
255+ } else {
256+ percentRemaining = Math .min (completionsQuota .percentRemaining (), chatQuota .percentRemaining ());
257+ }
258+
259+ ImageDescriptor icon = MenuUtils .getUsageIcon (percentRemaining );
260+ ImageDescriptor blankIcon = MenuUtils .getBlankIcon ();
261+
262+ Map <String , String > parameters = Map .of (UiConstants .OPEN_URL_PARAMETER_NAME , UiConstants .MANAGE_COPILOT_URL );
263+ items .add (createCommandItem (UiConstants .OPEN_URL_COMMAND_ID , Messages .menu_quota_copilotUsage , parameters ,
264+ Messages .menu_quota_manageCopilotTooltip , icon ));
265+
266+ GC gc = new GC (PlatformUI .getWorkbench ().getDisplay ());
267+ QuotaTextCalculator calculator = new QuotaTextCalculator (gc , quotaStatus );
268+ try {
269+ // Premium requests row first when both completion/chat quotas are unlimited.
270+ if (plan != CopilotPlan .free && premiumQuota != null && completionsQuota .unlimited () && chatQuota .unlimited ()) {
271+ this .premiumRequestsUsageItem = createCommandItem ("com.microsoft.copilot.eclipse.commands.enabledDoNothing" ,
272+ calculator .getPremiumText (), blankIcon );
273+ items .add (this .premiumRequestsUsageItem );
274+ }
275+
276+ // Code completions usage
277+ this .completionsUsageItem = createCommandItem ("com.microsoft.copilot.eclipse.commands.enabledDoNothing" ,
278+ calculator .getCompletionText (), blankIcon );
279+ items .add (this .completionsUsageItem );
280+
281+ // Chat messages usage
282+ this .chatUsageItem = createCommandItem ("com.microsoft.copilot.eclipse.commands.enabledDoNothing" ,
283+ calculator .getChatText (), blankIcon );
284+ items .add (this .chatUsageItem );
285+
286+ // Premium requests usage / additional-paid status for non-free plans.
287+ if (plan != CopilotPlan .free && premiumQuota != null ) {
288+ if (!completionsQuota .unlimited () || !chatQuota .unlimited ()) {
289+ this .premiumRequestsUsageItem = createCommandItem ("com.microsoft.copilot.eclipse.commands.enabledDoNothing" ,
290+ calculator .getPremiumText (), blankIcon );
291+ items .add (this .premiumRequestsUsageItem );
292+ }
293+
294+ CommandContributionItem additionalPremiumRequestsDesc = createCommandItem (
295+ "com.microsoft.copilot.eclipse.commands.disabledDoNothing" ,
296+ Messages .menu_quota_additionalPremiumRequests
297+ + (premiumQuota .overagePermitted () ? Messages .menu_quota_enabled : Messages .menu_quota_disabled ),
298+ null );
299+ items .add (additionalPremiumRequestsDesc );
300+ }
301+ } finally {
302+ gc .dispose ();
303+ }
304+
305+ // Allowance reset date (legacy: simple "Allowance resets <date>" string).
306+ if (!StringUtils .isEmpty (quotaStatus .resetDate ())) {
307+ LocalDate resetDate = LocalDate .parse (quotaStatus .resetDate ());
308+ DateTimeFormatter formatter = DateTimeFormatter .ofPattern ("MMMM dd, yyyy" );
309+ items .add (createCommandItem ("com.microsoft.copilot.eclipse.commands.disabledDoNothing" ,
310+ Messages .menu_quota_allowanceReset + resetDate .format (formatter ), null ));
311+ }
312+
313+ // Upsell actions based on the user's plan (legacy wording).
314+ ImageDescriptor upgradeIcon = UiUtils .buildImageDescriptorFromPngPath ("/icons/quota/upgrade.png" );
315+ if (plan == CopilotPlan .free ) {
316+ items .add (createCommandItem ("com.microsoft.copilot.eclipse.commands.upgradeCopilotPlan" ,
317+ Messages .menu_quota_updateCopilotToPro , Messages .menu_quota_updateCopilotToProPlus , upgradeIcon ));
318+ } else if (plan != CopilotPlan .business && plan != CopilotPlan .enterprise ) {
319+ items .add (createCommandItem (UiConstants .OPEN_URL_COMMAND_ID , Messages .menu_quota_managePaidPremiumRequests ,
320+ Map .of (UiConstants .OPEN_URL_PARAMETER_NAME , UiConstants .MANAGE_COPILOT_OVERAGE_URL ), upgradeIcon ));
321+ }
218322 }
219323
220324 private void addAuthenticationActions (List <IContributionItem > items , String status ) {
@@ -252,6 +356,7 @@ private void updateQuotaItems(CheckQuotaResult quotaResult) {
252356
253357 private void updateQuotaActionTexts (CheckQuotaResult quotaResult , GC gc ) {
254358 QuotaTextCalculator calculator = new QuotaTextCalculator (gc , quotaResult );
359+ boolean tbbEnabled = quotaResult .tokenBasedBillingEnabled ();
255360
256361 if (this .chatUsageItem != null && quotaResult .chat () != null ) {
257362 String chatMessagesText = calculator .getChatText ();
@@ -264,18 +369,23 @@ private void updateQuotaActionTexts(CheckQuotaResult quotaResult, GC gc) {
264369 }
265370
266371 if (this .premiumRequestsUsageItem != null && quotaResult .premiumInteractions () != null ) {
267- String monthlyLimitText = calculator .getPremiumRequestsText ();
268- setCommandItemField (this .premiumRequestsUsageItem , "label" , monthlyLimitText );
269- // Refresh the usage icon (red/yellow/blue) to reflect the latest percent remaining.
270- setCommandItemField (this .premiumRequestsUsageItem , "icon" ,
271- MenuUtils .getUsageIcon (MenuUtils .calculatePercentRemaining (quotaResult )));
372+ if (tbbEnabled ) {
373+ String monthlyLimitText = calculator .getPremiumRequestsText ();
374+ setCommandItemField (this .premiumRequestsUsageItem , "label" , monthlyLimitText );
375+ // Refresh the usage icon (red/yellow/blue) to reflect the latest percent remaining.
376+ setCommandItemField (this .premiumRequestsUsageItem , "icon" ,
377+ MenuUtils .getUsageIcon (MenuUtils .calculatePercentRemaining (quotaResult )));
378+ } else {
379+ // TODO: Remove this legacy fallback after TBB is officially released.
380+ setCommandItemField (this .premiumRequestsUsageItem , "label" , calculator .getPremiumText ());
381+ }
272382 }
273383
274384 // Refresh the allowance-reset row label, which switches between "Reset in N days..." and
275385 // "No usage yet" depending on whether any of the tracked quotas have been consumed. When the
276386 // predicate flips off (e.g. plan changed to unlimited mid-session), skip the update and leave
277387 // the stale label until the menu is rebuilt rather than rendering an empty disabled row.
278- if (this .allowanceResetItem != null && MenuUtils .shouldShowAllowanceResetRow (quotaResult )) {
388+ if (tbbEnabled && this .allowanceResetItem != null && MenuUtils .shouldShowAllowanceResetRow (quotaResult )) {
279389 setCommandItemField (this .allowanceResetItem , "label" , MenuUtils .formatAllowanceReset (quotaResult ));
280390 this .allowanceResetItem .update ();
281391 }
0 commit comments