Path: blob/main/extensions/copilot/src/platform/chat/common/chatQuotaServiceImpl.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter } from '../../../util/vs/base/common/event';6import { Disposable } from '../../../util/vs/base/common/lifecycle';7import { IAuthenticationService } from '../../authentication/common/authentication';8import { IHeaders } from '../../networking/common/fetcherService';9import { CopilotUserQuotaInfo, IChatQuota, IChatQuotaService, QuotaSnapshots } from './chatQuotaService';1011export class ChatQuotaService extends Disposable implements IChatQuotaService {12declare readonly _serviceBrand: undefined;1314private _quotaInfo: IChatQuota | undefined;15private _rateLimitInfo: { session: IChatQuota | undefined; weekly: IChatQuota | undefined };1617private readonly _onDidChange = this._register(new Emitter<void>());18readonly onDidChange = this._onDidChange.event;1920constructor(@IAuthenticationService private readonly _authService: IAuthenticationService) {21super();22this._rateLimitInfo = { session: undefined, weekly: undefined };23this._register(this._authService.onDidAuthenticationChange(() => {24this._processUserInfoQuotaSnapshot(this._authService.copilotToken?.quotaInfo);25}));26}2728get quotaInfo(): IChatQuota | undefined {29return this._quotaInfo;30}3132get rateLimitInfo(): { readonly session: IChatQuota | undefined; readonly weekly: IChatQuota | undefined } {33return this._rateLimitInfo;34}3536get quotaExhausted(): boolean {37if (!this._quotaInfo) {38return false;39}40return this._quotaInfo.percentRemaining <= 0 && !this._quotaInfo.additionalUsageEnabled && !this._quotaInfo.unlimited;41}4243get additionalUsageEnabled(): boolean {44if (!this._quotaInfo) {45return false;46}47return this._quotaInfo.additionalUsageEnabled;48}4950clearQuota(): void {51this._quotaInfo = undefined;52}5354processQuotaHeaders(headers: IHeaders): void {55const 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');56if (!quotaHeader) {57return;58}59const quotaInfo = this._processHeaderValue(quotaHeader);60if (!quotaInfo) {61return;62}63this._quotaInfo = quotaInfo;64const sessionRateLimitHeader = headers.get('x-usage-ratelimit-session');65const weeklyRateLimitHeader = headers.get('x-usage-ratelimit-weekly');66this._rateLimitInfo.session = sessionRateLimitHeader ? this._processHeaderValue(sessionRateLimitHeader) : undefined;67this._rateLimitInfo.weekly = weeklyRateLimitHeader ? this._processHeaderValue(weeklyRateLimitHeader) : undefined;68this._onDidChange.fire();69}7071processQuotaSnapshots(snapshots: QuotaSnapshots): void {72const snapshot = this._authService.copilotToken?.isFreeUser73? snapshots['chat']74: snapshots['premium_models'] ?? snapshots['premium_interactions'];75if (!snapshot) {76return;77}7879try {80const entitlement = parseInt(snapshot.entitlement, 10);81const resetDate = snapshot.reset_date ? new Date(snapshot.reset_date) : (() => { const d = new Date(); d.setMonth(d.getMonth() + 1); return d; })();8283this._quotaInfo = {84quota: entitlement,85unlimited: entitlement === -1,86percentRemaining: snapshot.percent_remaining,87additionalUsageUsed: snapshot.overage_count,88additionalUsageEnabled: snapshot.overage_permitted,89resetDate90};91this._onDidChange.fire();92} catch (error) {93console.error('Failed to process quota snapshots', error);94}95}9697private _processHeaderValue(header: string): IChatQuota | undefined {98try {99// Parse URL encoded string into key-value pairs100const params = new URLSearchParams(header);101102// Extract values with fallbacks to ensure type safety103const entitlement = parseInt(params.get('ent') || '0', 10);104const additionalUsageUsed = parseFloat(params.get('ov') || '0.0');105const additionalUsageEnabled = params.get('ovPerm') === 'true';106const percentRemaining = parseFloat(params.get('rem') || '0.0');107const resetDateString = params.get('rst');108109let resetDate: Date;110if (resetDateString) {111resetDate = new Date(resetDateString);112} else {113// Default to one month from now if not provided114resetDate = new Date();115resetDate.setMonth(resetDate.getMonth() + 1);116}117118return {119quota: entitlement,120unlimited: entitlement === -1,121percentRemaining,122additionalUsageUsed,123additionalUsageEnabled,124resetDate125};126} catch (error) {127console.error('Failed to parse quota header', error);128return undefined;129}130}131132private _processUserInfoQuotaSnapshot(quotaInfo: CopilotUserQuotaInfo | undefined) {133if (!quotaInfo || !quotaInfo.quota_snapshots || !quotaInfo.quota_reset_date) {134return;135}136this._quotaInfo = {137unlimited: quotaInfo.quota_snapshots.premium_interactions.unlimited,138additionalUsageEnabled: quotaInfo.quota_snapshots.premium_interactions.overage_permitted,139additionalUsageUsed: quotaInfo.quota_snapshots.premium_interactions.overage_count,140quota: quotaInfo.quota_snapshots.premium_interactions.entitlement,141resetDate: new Date(quotaInfo.quota_reset_date),142percentRemaining: quotaInfo.quota_snapshots.premium_interactions.percent_remaining,143};144this._onDidChange.fire();145}146}147148149