Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/chat/common/chatQuotaServiceImpl.ts
13401 views
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 { Emitter } from '../../../util/vs/base/common/event';
7
import { Disposable } from '../../../util/vs/base/common/lifecycle';
8
import { IAuthenticationService } from '../../authentication/common/authentication';
9
import { IHeaders } from '../../networking/common/fetcherService';
10
import { CopilotUserQuotaInfo, IChatQuota, IChatQuotaService, QuotaSnapshots } from './chatQuotaService';
11
12
export class ChatQuotaService extends Disposable implements IChatQuotaService {
13
declare readonly _serviceBrand: undefined;
14
15
private _quotaInfo: IChatQuota | undefined;
16
private _rateLimitInfo: { session: IChatQuota | undefined; weekly: IChatQuota | undefined };
17
18
private readonly _onDidChange = this._register(new Emitter<void>());
19
readonly onDidChange = this._onDidChange.event;
20
21
constructor(@IAuthenticationService private readonly _authService: IAuthenticationService) {
22
super();
23
this._rateLimitInfo = { session: undefined, weekly: undefined };
24
this._register(this._authService.onDidAuthenticationChange(() => {
25
this._processUserInfoQuotaSnapshot(this._authService.copilotToken?.quotaInfo);
26
}));
27
}
28
29
get quotaInfo(): IChatQuota | undefined {
30
return this._quotaInfo;
31
}
32
33
get rateLimitInfo(): { readonly session: IChatQuota | undefined; readonly weekly: IChatQuota | undefined } {
34
return this._rateLimitInfo;
35
}
36
37
get quotaExhausted(): boolean {
38
if (!this._quotaInfo) {
39
return false;
40
}
41
return this._quotaInfo.percentRemaining <= 0 && !this._quotaInfo.additionalUsageEnabled && !this._quotaInfo.unlimited;
42
}
43
44
get additionalUsageEnabled(): boolean {
45
if (!this._quotaInfo) {
46
return false;
47
}
48
return this._quotaInfo.additionalUsageEnabled;
49
}
50
51
clearQuota(): void {
52
this._quotaInfo = undefined;
53
}
54
55
processQuotaHeaders(headers: IHeaders): void {
56
const quotaHeader = this._authService.copilotToken?.isFreeUser ? headers.get('x-quota-snapshot-chat') : headers.get('x-quota-snapshot-premium_models') || headers.get('x-quota-snapshot-premium_interactions');
57
if (!quotaHeader) {
58
return;
59
}
60
const quotaInfo = this._processHeaderValue(quotaHeader);
61
if (!quotaInfo) {
62
return;
63
}
64
this._quotaInfo = quotaInfo;
65
const sessionRateLimitHeader = headers.get('x-usage-ratelimit-session');
66
const weeklyRateLimitHeader = headers.get('x-usage-ratelimit-weekly');
67
this._rateLimitInfo.session = sessionRateLimitHeader ? this._processHeaderValue(sessionRateLimitHeader) : undefined;
68
this._rateLimitInfo.weekly = weeklyRateLimitHeader ? this._processHeaderValue(weeklyRateLimitHeader) : undefined;
69
this._onDidChange.fire();
70
}
71
72
processQuotaSnapshots(snapshots: QuotaSnapshots): void {
73
const snapshot = this._authService.copilotToken?.isFreeUser
74
? snapshots['chat']
75
: snapshots['premium_models'] ?? snapshots['premium_interactions'];
76
if (!snapshot) {
77
return;
78
}
79
80
try {
81
const entitlement = parseInt(snapshot.entitlement, 10);
82
const resetDate = snapshot.reset_date ? new Date(snapshot.reset_date) : (() => { const d = new Date(); d.setMonth(d.getMonth() + 1); return d; })();
83
84
this._quotaInfo = {
85
quota: entitlement,
86
unlimited: entitlement === -1,
87
percentRemaining: snapshot.percent_remaining,
88
additionalUsageUsed: snapshot.overage_count,
89
additionalUsageEnabled: snapshot.overage_permitted,
90
resetDate
91
};
92
this._onDidChange.fire();
93
} catch (error) {
94
console.error('Failed to process quota snapshots', error);
95
}
96
}
97
98
private _processHeaderValue(header: string): IChatQuota | undefined {
99
try {
100
// Parse URL encoded string into key-value pairs
101
const params = new URLSearchParams(header);
102
103
// Extract values with fallbacks to ensure type safety
104
const entitlement = parseInt(params.get('ent') || '0', 10);
105
const additionalUsageUsed = parseFloat(params.get('ov') || '0.0');
106
const additionalUsageEnabled = params.get('ovPerm') === 'true';
107
const percentRemaining = parseFloat(params.get('rem') || '0.0');
108
const resetDateString = params.get('rst');
109
110
let resetDate: Date;
111
if (resetDateString) {
112
resetDate = new Date(resetDateString);
113
} else {
114
// Default to one month from now if not provided
115
resetDate = new Date();
116
resetDate.setMonth(resetDate.getMonth() + 1);
117
}
118
119
return {
120
quota: entitlement,
121
unlimited: entitlement === -1,
122
percentRemaining,
123
additionalUsageUsed,
124
additionalUsageEnabled,
125
resetDate
126
};
127
} catch (error) {
128
console.error('Failed to parse quota header', error);
129
return undefined;
130
}
131
}
132
133
private _processUserInfoQuotaSnapshot(quotaInfo: CopilotUserQuotaInfo | undefined) {
134
if (!quotaInfo || !quotaInfo.quota_snapshots || !quotaInfo.quota_reset_date) {
135
return;
136
}
137
this._quotaInfo = {
138
unlimited: quotaInfo.quota_snapshots.premium_interactions.unlimited,
139
additionalUsageEnabled: quotaInfo.quota_snapshots.premium_interactions.overage_permitted,
140
additionalUsageUsed: quotaInfo.quota_snapshots.premium_interactions.overage_count,
141
quota: quotaInfo.quota_snapshots.premium_interactions.entitlement,
142
resetDate: new Date(quotaInfo.quota_reset_date),
143
percentRemaining: quotaInfo.quota_snapshots.premium_interactions.percent_remaining,
144
};
145
this._onDidChange.fire();
146
}
147
}
148
149