Skip to main content
Glama
enforcer.ts8.01 kB
/** * Quota Enforcer - Daily quota checking and enforcement */ import { Pool } from 'pg'; import type { QuotaCheck } from '../types/tracing.js'; export class QuotaEnforcer { constructor(private db: Pool) { } /** * Check if user has quota available for a request */ async checkQuota( userId: string, projectId: string, estimatedTokens: number, estimatedCost: number, ): Promise<QuotaCheck> { // Get or create user quota const quota = await this.getUserQuota(userId, projectId); if (!quota) { return { allowed: false, remaining: { tokens: 0, cost: 0 }, resetAt: new Date(), reason: 'Quota not configured for user', }; } // Check if quota needs reset (daily reset) if (new Date() >= quota.resetAt) { await this.resetQuota(userId, projectId); // Refresh quota data const refreshed = await this.getUserQuota(userId, projectId); if (!refreshed) { return { allowed: false, remaining: { tokens: 0, cost: 0 }, resetAt: new Date(), reason: 'Failed to refresh quota', }; } quota.currentTokensToday = refreshed.currentTokensToday; quota.currentCostToday = refreshed.currentCostToday; } // Check token quota const tokenRemaining = quota.maxTokensDaily - quota.currentTokensToday; if (estimatedTokens > tokenRemaining) { return { allowed: false, remaining: { tokens: tokenRemaining, cost: quota.maxCostDaily - quota.currentCostToday, }, resetAt: quota.resetAt, reason: `Token quota exceeded. Remaining: ${tokenRemaining}, Requested: ${estimatedTokens}`, }; } // Check cost quota const costRemaining = quota.maxCostDaily - quota.currentCostToday; if (estimatedCost > costRemaining) { return { allowed: false, remaining: { tokens: tokenRemaining, cost: costRemaining, }, resetAt: quota.resetAt, reason: `Cost quota exceeded. Remaining: $${costRemaining.toFixed(4)}, Requested: $${estimatedCost.toFixed(4)}`, }; } // Quota check passed return { allowed: true, remaining: { tokens: tokenRemaining - estimatedTokens, cost: costRemaining - estimatedCost, }, resetAt: quota.resetAt, }; } /** * Increment user quota usage */ async incrementQuota( userId: string, projectId: string, tokens: number, cost: number, ): Promise<void> { await this.db.query( `UPDATE user_quotas SET current_tokens_today = current_tokens_today + $1, current_cost_today = current_cost_today + $2, updated_at = NOW() WHERE user_id = $3 AND project_id = $4`, [tokens, cost, userId, projectId], ); } /** * Get user quota or create default if missing */ private async getUserQuota( userId: string, projectId: string, ): Promise<{ maxTokensDaily: number; maxCostDaily: number; currentTokensToday: number; currentCostToday: number; resetAt: Date; } | null> { const result = await this.db.query( `SELECT max_tokens_daily, max_cost_daily, current_tokens_today, current_cost_today, reset_at FROM user_quotas WHERE user_id = $1 AND project_id = $2`, [userId, projectId], ); if (result.rows.length === 0) { // Create default quota (1M tokens, $10 per day) await this.createDefaultQuota(userId, projectId); return this.getUserQuota(userId, projectId); } const row = result.rows[0]; return { maxTokensDaily: row.max_tokens_daily, maxCostDaily: parseFloat(row.max_cost_daily), currentTokensToday: row.current_tokens_today, currentCostToday: parseFloat(row.current_cost_today), resetAt: row.reset_at, }; } /** * Create default quota for new user */ private async createDefaultQuota( userId: string, projectId: string, ): Promise<void> { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); await this.db.query( `INSERT INTO user_quotas ( user_id, project_id, max_tokens_daily, max_cost_daily, current_tokens_today, current_cost_today, reset_at ) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (user_id, project_id) DO NOTHING`, [ userId, projectId, 1_000_000, // 1M tokens 10.0, // $10 0, 0, tomorrow, ], ); } /** * Reset quota to 0 and set next reset time */ private async resetQuota( userId: string, projectId: string, ): Promise<void> { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(0, 0, 0, 0); await this.db.query( `UPDATE user_quotas SET current_tokens_today = 0, current_cost_today = 0, reset_at = $1, updated_at = NOW() WHERE user_id = $2 AND project_id = $3`, [tomorrow, userId, projectId], ); } /** * Update quota limits for a user */ async updateQuotaLimits( userId: string, projectId: string, maxTokensDaily: number, maxCostDaily: number, ): Promise<void> { await this.db.query( `UPDATE user_quotas SET max_tokens_daily = $1, max_cost_daily = $2, updated_at = NOW() WHERE user_id = $3 AND project_id = $4`, [maxTokensDaily, maxCostDaily, userId, projectId], ); } /** * Get quota status for a user */ async getQuotaStatus( userId: string, projectId: string, ): Promise<{ maxTokensDaily: number; maxCostDaily: number; currentTokensToday: number; currentCostToday: number; remainingTokens: number; remainingCost: number; usagePercentage: { tokens: number; cost: number; }; resetAt: Date; } | null> { const quota = await this.getUserQuota(userId, projectId); if (!quota) { return null; } const remainingTokens = quota.maxTokensDaily - quota.currentTokensToday; const remainingCost = quota.maxCostDaily - quota.currentCostToday; return { maxTokensDaily: quota.maxTokensDaily, maxCostDaily: quota.maxCostDaily, currentTokensToday: quota.currentTokensToday, currentCostToday: quota.currentCostToday, remainingTokens, remainingCost, usagePercentage: { tokens: (quota.currentTokensToday / quota.maxTokensDaily) * 100, cost: (quota.currentCostToday / quota.maxCostDaily) * 100, }, resetAt: quota.resetAt, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server