Skip to content

Commit a3bc574

Browse files
committed
feat: Implement TBB quota warning notifications and related UI components
1 parent d269a2b commit a3bc574

19 files changed

Lines changed: 571 additions & 7 deletions

File tree

com.microsoft.copilot.eclipse.core.test/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClientTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
import static org.mockito.Mockito.verify;
1111
import static org.mockito.Mockito.when;
1212

13+
import java.lang.reflect.Field;
1314
import java.util.HashMap;
1415
import java.util.Map;
1516
import java.util.concurrent.CompletableFuture;
1617
import java.util.concurrent.ExecutionException;
1718

1819
import org.eclipse.core.resources.IFile;
20+
import org.eclipse.e4.core.services.events.IEventBroker;
1921
import org.junit.jupiter.api.BeforeEach;
2022
import org.junit.jupiter.api.Test;
2123
import org.junit.jupiter.api.extension.ExtendWith;
@@ -28,10 +30,12 @@
2830
import com.microsoft.copilot.eclipse.core.FeatureFlags;
2931
import com.microsoft.copilot.eclipse.core.chat.service.IChatServiceManager;
3032
import com.microsoft.copilot.eclipse.core.chat.service.IReferencedFileService;
33+
import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants;
3134
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationCapabilities;
3235
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationContextParams;
3336
import com.microsoft.copilot.eclipse.core.lsp.protocol.CurrentEditorContext;
3437
import com.microsoft.copilot.eclipse.core.lsp.protocol.DidChangeFeatureFlagsParams;
38+
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.QuotaWarningNotification;
3539
import com.microsoft.copilot.eclipse.core.utils.FileUtils;
3640

3741
@ExtendWith(MockitoExtension.class)
@@ -48,6 +52,9 @@ class CopilotLanguageClientTests {
4852
@Mock
4953
private IReferencedFileService fileService;
5054

55+
@Mock
56+
private IEventBroker eventBroker;
57+
5158
@BeforeEach
5259
void setUp() {
5360
client = new CopilotLanguageClient();
@@ -131,4 +138,24 @@ void testOnDidChangeFeatureFlagsWithEmptyFeatureFlags() {
131138
verify(mockFeatureFlags).setByokEnabled(true);
132139
}
133140
}
141+
142+
@Test
143+
void testOnQuotaWarning_PostsNotificationToEventBroker() {
144+
QuotaWarningNotification notification = new QuotaWarningNotification("Approaching quota", 90.0);
145+
setEventBroker(eventBroker);
146+
147+
client.onQuotaWarning(notification);
148+
149+
verify(eventBroker).post(CopilotEventConstants.TOPIC_QUOTA_WARNING, notification);
150+
}
151+
152+
private void setEventBroker(IEventBroker broker) {
153+
try {
154+
Field field = CopilotLanguageClient.class.getDeclaredField("eventBroker");
155+
field.setAccessible(true);
156+
field.set(client, broker);
157+
} catch (ReflectiveOperationException e) {
158+
throw new AssertionError("Failed to inject event broker", e);
159+
}
160+
}
134161
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/events/CopilotEventConstants.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public class CopilotEventConstants {
3030
*/
3131
private static final String TOPIC_MCP = TOPIC_BASE + "MCP/";
3232

33+
/**
34+
* Topic for quota events.
35+
*/
36+
private static final String TOPIC_QUOTA = TOPIC_BASE + "QUOTA/";
37+
3338
/**
3439
* Topic for Next Edit Suggestion (NES) events.
3540
*/
@@ -160,4 +165,9 @@ public class CopilotEventConstants {
160165
* Event when a rate limit warning is received from the language server.
161166
*/
162167
public static final String TOPIC_RATE_LIMIT_WARNING = TOPIC_CHAT + "RATE_LIMIT_WARNING";
168+
169+
/**
170+
* Event when a quota warning notification is received from the language server.
171+
*/
172+
public static final String TOPIC_QUOTA_WARNING = TOPIC_QUOTA + "WARNING";
163173
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClient.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import com.microsoft.copilot.eclipse.core.lsp.protocol.codingagent.CodingAgentMessageRequestParams;
6565
import com.microsoft.copilot.eclipse.core.lsp.protocol.codingagent.CodingAgentMessageResult;
6666
import com.microsoft.copilot.eclipse.core.lsp.protocol.policy.DidChangePolicyParams;
67+
import com.microsoft.copilot.eclipse.core.lsp.protocol.quota.QuotaWarningNotification;
6768
import com.microsoft.copilot.eclipse.core.utils.FileUtils;
6869
import com.microsoft.copilot.eclipse.core.utils.PlatformUtils;
6970

@@ -332,6 +333,17 @@ public CompletableFuture<CodingAgentMessageResult> onCodingAgentMessage(CodingAg
332333
return CompletableFuture.completedFuture(result);
333334
}
334335

336+
/**
337+
* Notify when a quota warning is received from the language server.
338+
*/
339+
@JsonNotification("copilot/quotaWarning")
340+
public void onQuotaWarning(QuotaWarningNotification notification) {
341+
CopilotCore.LOGGER.info("Quota warning received: " + notification);
342+
if (eventBroker != null) {
343+
eventBroker.post(CopilotEventConstants.TOPIC_QUOTA_WARNING, notification);
344+
}
345+
}
346+
335347
/**
336348
* Reads the contents and stats of a file given its URI.
337349
*/

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/quota/CheckQuotaResult.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public class CheckQuotaResult {
1414
private Quota chat;
1515
private Quota completions;
1616
private Quota premiumInteractions;
17+
private IntervalQuota immediateUsageInterval;
18+
private IntervalQuota extendedUsageInterval;
1719
private String resetDate;
1820
private CopilotPlan copilotPlan;
1921

@@ -41,6 +43,28 @@ public void setPremiumInteractionsQuota(Quota premiumInteractions) {
4143
this.premiumInteractions = premiumInteractions;
4244
}
4345

46+
/**
47+
* Gets the immediate usage interval quota (for individual plans).
48+
*/
49+
public IntervalQuota getImmediateUsageInterval() {
50+
return immediateUsageInterval;
51+
}
52+
53+
public void setImmediateUsageInterval(IntervalQuota immediateUsageInterval) {
54+
this.immediateUsageInterval = immediateUsageInterval;
55+
}
56+
57+
/**
58+
* Gets the extended usage interval quota (for individual plans).
59+
*/
60+
public IntervalQuota getExtendedUsageInterval() {
61+
return extendedUsageInterval;
62+
}
63+
64+
public void setExtendedUsageInterval(IntervalQuota extendedUsageInterval) {
65+
this.extendedUsageInterval = extendedUsageInterval;
66+
}
67+
4468
public String getResetDate() {
4569
return resetDate;
4670
}
@@ -59,7 +83,8 @@ public void setCopilotPlan(CopilotPlan copilotPlan) {
5983

6084
@Override
6185
public int hashCode() {
62-
return Objects.hash(chat, completions, copilotPlan, premiumInteractions, resetDate);
86+
return Objects.hash(chat, completions, copilotPlan, extendedUsageInterval,
87+
immediateUsageInterval, premiumInteractions, resetDate);
6388
}
6489

6590
@Override
@@ -75,7 +100,10 @@ public boolean equals(Object obj) {
75100
}
76101
CheckQuotaResult other = (CheckQuotaResult) obj;
77102
return Objects.equals(chat, other.chat) && Objects.equals(completions, other.completions)
78-
&& copilotPlan == other.copilotPlan && Objects.equals(premiumInteractions, other.premiumInteractions)
103+
&& copilotPlan == other.copilotPlan
104+
&& Objects.equals(extendedUsageInterval, other.extendedUsageInterval)
105+
&& Objects.equals(immediateUsageInterval, other.immediateUsageInterval)
106+
&& Objects.equals(premiumInteractions, other.premiumInteractions)
79107
&& Objects.equals(resetDate, other.resetDate);
80108
}
81109

@@ -85,6 +113,8 @@ public String toString() {
85113
builder.append("chat", chat);
86114
builder.append("completions", completions);
87115
builder.append("premiumInteractions", premiumInteractions);
116+
builder.append("immediateUsageInterval", immediateUsageInterval);
117+
builder.append("extendedUsageInterval", extendedUsageInterval);
88118
builder.append("resetDate", resetDate);
89119
builder.append("copilotPlan", copilotPlan);
90120
return builder.toString();

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/quota/CopilotPlan.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
* Enum representing the different Copilot plans.
88
*/
99
public enum CopilotPlan {
10-
free, individual, individual_pro, business, enterprise
10+
free, individual, individual_pro, individual_max, business, enterprise
1111
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package com.microsoft.copilot.eclipse.core.lsp.protocol.quota;
5+
6+
/**
7+
* Interval-based quota information, used for immediateUsageInterval and extendedUsageInterval.
8+
*/
9+
public record IntervalQuota(
10+
double percentRemaining,
11+
boolean unlimited,
12+
boolean overagePermitted,
13+
Integer entitlement,
14+
Integer quotaRemaining,
15+
String timeStamp,
16+
String resetAt) {
17+
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/quota/Quota.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public class Quota {
1414
private double percentRemaining;
1515
private boolean unlimited;
1616
private boolean overagePermitted;
17+
private Integer entitlement;
18+
private Integer quotaRemaining;
19+
private String timeStamp;
1720

1821
/**
1922
* Creates a new CompletionsQuota quota information with default values.
@@ -56,9 +59,42 @@ public void setOveragePermitted(boolean overagePermitted) {
5659
this.overagePermitted = overagePermitted;
5760
}
5861

62+
/**
63+
* Gets the total entitlement (quota limit).
64+
*/
65+
public Integer getEntitlement() {
66+
return entitlement;
67+
}
68+
69+
public void setEntitlement(Integer entitlement) {
70+
this.entitlement = entitlement;
71+
}
72+
73+
/**
74+
* Gets the remaining quota count.
75+
*/
76+
public Integer getQuotaRemaining() {
77+
return quotaRemaining;
78+
}
79+
80+
public void setQuotaRemaining(Integer quotaRemaining) {
81+
this.quotaRemaining = quotaRemaining;
82+
}
83+
84+
/**
85+
* Gets the timestamp of the quota snapshot.
86+
*/
87+
public String getTimeStamp() {
88+
return timeStamp;
89+
}
90+
91+
public void setTimeStamp(String timeStamp) {
92+
this.timeStamp = timeStamp;
93+
}
94+
5995
@Override
6096
public int hashCode() {
61-
return Objects.hash(overagePermitted, percentRemaining, unlimited);
97+
return Objects.hash(entitlement, overagePermitted, percentRemaining, quotaRemaining, timeStamp, unlimited);
6298
}
6399

64100
@Override
@@ -73,8 +109,9 @@ public boolean equals(Object obj) {
73109
return false;
74110
}
75111
Quota other = (Quota) obj;
76-
return overagePermitted == other.overagePermitted
112+
return Objects.equals(entitlement, other.entitlement) && overagePermitted == other.overagePermitted
77113
&& Double.doubleToLongBits(percentRemaining) == Double.doubleToLongBits(other.percentRemaining)
114+
&& Objects.equals(quotaRemaining, other.quotaRemaining) && Objects.equals(timeStamp, other.timeStamp)
78115
&& unlimited == other.unlimited;
79116
}
80117

@@ -84,6 +121,9 @@ public String toString() {
84121
builder.append("percentRemaining", percentRemaining);
85122
builder.append("unlimited", unlimited);
86123
builder.append("overagePermitted", overagePermitted);
124+
builder.append("entitlement", entitlement);
125+
builder.append("quotaRemaining", quotaRemaining);
126+
builder.append("timeStamp", timeStamp);
87127
return builder.toString();
88128
}
89129
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package com.microsoft.copilot.eclipse.core.lsp.protocol.quota;
5+
6+
import com.google.gson.annotations.SerializedName;
7+
8+
/**
9+
* Parameters for the {@code copilot/quotaChange} notification, sent by the language server
10+
* whenever the user's quota usage changes.
11+
*
12+
* @param chat current chat quota snapshot, when available
13+
* @param completions current completions quota snapshot, when available
14+
* @param premiumInteractions current premium interactions quota snapshot, when available
15+
* @param copilotPlan the user's Copilot plan (e.g. free, individual, individual_pro, individual_max,
16+
* business, enterprise)
17+
*/
18+
public record QuotaChangeParams(QuotaSnapshotParams chat, QuotaSnapshotParams completions,
19+
@SerializedName("premium_interactions") QuotaSnapshotParams premiumInteractions, String copilotPlan) {
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package com.microsoft.copilot.eclipse.core.lsp.protocol.quota;
5+
6+
/**
7+
* Parameters for the "copilot/quotaWarning" notification. Sent by the language server when the user's AI quota exceeds
8+
* the warning threshold.
9+
*/
10+
public record QuotaWarningNotification(String message, double percentUsed) {
11+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
package com.microsoft.copilot.eclipse.core.lsp.protocol.quota;
5+
6+
import com.google.gson.annotations.SerializedName;
7+
8+
/**
9+
* Parameters for the {@code copilot/quotaWarning} notification, sent by the language server when
10+
* the user crosses a quota usage threshold or starts consuming overages.
11+
*
12+
* @param title warning title (e.g. "Copilot Quota Usage Alert")
13+
* @param message human-readable warning message
14+
* @param severity severity level, either {@code "warning"} or {@code "info"}
15+
* @param chat current chat quota snapshot, when available
16+
* @param completions current completions quota snapshot, when available
17+
* @param premiumInteractions current premium interactions quota snapshot, when available
18+
* @param copilotPlan the user's Copilot plan
19+
*/
20+
public record QuotaWarningParams(String title, String message, String severity, QuotaSnapshotParams chat,
21+
QuotaSnapshotParams completions,
22+
@SerializedName("premium_interactions") QuotaSnapshotParams premiumInteractions, String copilotPlan) {
23+
}

0 commit comments

Comments
 (0)