@callmedayz/ai-prompt-toolkit
Version:
Professional AI prompt engineering toolkit with advanced template features, real-time dashboards, conditional logic, template inheritance, live monitoring, OpenRouter integration, and 310+ model support
368 lines • 13.4 kB
JavaScript
"use strict";
/**
* Rate limiting and quota management for OpenRouter API
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.QuotaManager = exports.RateLimiter = exports.DEFAULT_RATE_LIMITS = void 0;
/**
* Default rate limits (conservative defaults)
*/
exports.DEFAULT_RATE_LIMITS = {
requestsPerMinute: 60,
requestsPerHour: 1000,
requestsPerDay: 10000,
tokensPerMinute: 10000,
tokensPerHour: 100000,
tokensPerDay: 1000000,
costPerMinute: 1.0,
costPerHour: 10.0,
costPerDay: 100.0
};
/**
* Token bucket implementation for rate limiting
*/
class TokenBucket {
constructor(capacity, refillRate, // tokens per second
refillInterval = 1000 // milliseconds
) {
this.capacity = capacity;
this.refillRate = refillRate;
this.refillInterval = refillInterval;
this.tokens = capacity;
this.lastRefill = Date.now();
}
consume(tokens = 1) {
this.refill();
if (this.tokens >= tokens) {
this.tokens -= tokens;
return true;
}
return false;
}
getAvailableTokens() {
this.refill();
return this.tokens;
}
refill() {
const now = Date.now();
const timePassed = now - this.lastRefill;
const tokensToAdd = (timePassed / this.refillInterval) * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
/**
* Rate limiter with multiple time windows
*/
class RateLimiter {
constructor(config) {
this.config = config;
this.usage = this.initializeUsage();
this.buckets = {
minute: new TokenBucket(config.requestsPerMinute, config.requestsPerMinute / 60),
hour: new TokenBucket(config.requestsPerHour, config.requestsPerHour / 3600),
day: new TokenBucket(config.requestsPerDay, config.requestsPerDay / 86400)
};
}
/**
* Check if a request is allowed
*/
checkRequest(tokens = 0, cost = 0) {
this.resetCountersIfNeeded();
// Check request limits
if (!this.buckets.minute.consume(1)) {
return {
allowed: false,
reason: 'Minute request limit exceeded',
retryAfter: 60000,
remaining: this.getRemainingLimits()
};
}
if (!this.buckets.hour.consume(1)) {
return {
allowed: false,
reason: 'Hour request limit exceeded',
retryAfter: 3600000,
remaining: this.getRemainingLimits()
};
}
if (!this.buckets.day.consume(1)) {
return {
allowed: false,
reason: 'Day request limit exceeded',
retryAfter: 86400000,
remaining: this.getRemainingLimits()
};
}
// Check token limits
if (tokens > 0 && this.config.tokensPerMinute) {
if (this.usage.tokens.minute + tokens > this.config.tokensPerMinute) {
return {
allowed: false,
reason: 'Minute token limit exceeded',
retryAfter: 60000,
remaining: this.getRemainingLimits()
};
}
}
// Check cost limits
if (cost > 0 && this.config.costPerMinute) {
if (this.usage.cost.minute + cost > this.config.costPerMinute) {
return {
allowed: false,
reason: 'Minute cost limit exceeded',
retryAfter: 60000,
remaining: this.getRemainingLimits()
};
}
}
return {
allowed: true,
remaining: this.getRemainingLimits()
};
}
/**
* Record usage after a successful request
*/
recordUsage(tokens = 0, cost = 0) {
this.resetCountersIfNeeded();
this.usage.requests.minute++;
this.usage.requests.hour++;
this.usage.requests.day++;
this.usage.tokens.minute += tokens;
this.usage.tokens.hour += tokens;
this.usage.tokens.day += tokens;
this.usage.cost.minute += cost;
this.usage.cost.hour += cost;
this.usage.cost.day += cost;
}
/**
* Get current usage statistics
*/
getUsage() {
this.resetCountersIfNeeded();
return { ...this.usage };
}
/**
* Get remaining limits
*/
getRemainingLimits() {
return {
requests: {
minute: Math.max(0, this.config.requestsPerMinute - this.usage.requests.minute),
hour: Math.max(0, this.config.requestsPerHour - this.usage.requests.hour),
day: Math.max(0, this.config.requestsPerDay - this.usage.requests.day)
},
tokens: this.config.tokensPerMinute ? {
minute: Math.max(0, this.config.tokensPerMinute - this.usage.tokens.minute),
hour: Math.max(0, (this.config.tokensPerHour || 0) - this.usage.tokens.hour),
day: Math.max(0, (this.config.tokensPerDay || 0) - this.usage.tokens.day)
} : undefined,
cost: this.config.costPerMinute ? {
minute: Math.max(0, this.config.costPerMinute - this.usage.cost.minute),
hour: Math.max(0, (this.config.costPerHour || 0) - this.usage.cost.hour),
day: Math.max(0, (this.config.costPerDay || 0) - this.usage.cost.day)
} : undefined
};
}
/**
* Reset counters when time windows expire
*/
resetCountersIfNeeded() {
const now = Date.now();
// Reset minute counter
if (now - this.usage.lastReset.minute >= 60000) {
this.usage.requests.minute = 0;
this.usage.tokens.minute = 0;
this.usage.cost.minute = 0;
this.usage.lastReset.minute = now;
}
// Reset hour counter
if (now - this.usage.lastReset.hour >= 3600000) {
this.usage.requests.hour = 0;
this.usage.tokens.hour = 0;
this.usage.cost.hour = 0;
this.usage.lastReset.hour = now;
}
// Reset day counter
if (now - this.usage.lastReset.day >= 86400000) {
this.usage.requests.day = 0;
this.usage.tokens.day = 0;
this.usage.cost.day = 0;
this.usage.lastReset.day = now;
}
}
/**
* Initialize usage tracking
*/
initializeUsage() {
const now = Date.now();
return {
requests: { minute: 0, hour: 0, day: 0 },
tokens: { minute: 0, hour: 0, day: 0 },
cost: { minute: 0, hour: 0, day: 0 },
lastReset: { minute: now, hour: now, day: now }
};
}
/**
* Update rate limit configuration
*/
updateConfig(config) {
this.config = { ...this.config, ...config };
// Update token buckets
this.buckets.minute = new TokenBucket(this.config.requestsPerMinute, this.config.requestsPerMinute / 60);
this.buckets.hour = new TokenBucket(this.config.requestsPerHour, this.config.requestsPerHour / 3600);
this.buckets.day = new TokenBucket(this.config.requestsPerDay, this.config.requestsPerDay / 86400);
}
}
exports.RateLimiter = RateLimiter;
/**
* Quota manager for cost control
*/
class QuotaManager {
constructor(config) {
this.config = config;
this.alerts = [];
this.usage = this.initializeUsage();
}
/**
* Check if a request is within quota
*/
checkQuota(estimatedCost) {
this.resetQuotasIfNeeded();
// Check daily quota
if (this.usage.daily.spent + estimatedCost > this.config.dailyBudget) {
const alert = {
type: 'exceeded',
period: 'daily',
threshold: 100,
current: this.usage.daily.spent + estimatedCost,
budget: this.config.dailyBudget,
message: `Daily budget of $${this.config.dailyBudget} would be exceeded`
};
return { allowed: !this.config.autoStop, alert };
}
// Check monthly quota
if (this.usage.monthly.spent + estimatedCost > this.config.monthlyBudget) {
const alert = {
type: 'exceeded',
period: 'monthly',
threshold: 100,
current: this.usage.monthly.spent + estimatedCost,
budget: this.config.monthlyBudget,
message: `Monthly budget of $${this.config.monthlyBudget} would be exceeded`
};
return { allowed: !this.config.autoStop, alert };
}
// Check for threshold alerts
const alert = this.checkThresholds(estimatedCost);
return { allowed: true, alert };
}
/**
* Record actual spending
*/
recordSpending(actualCost) {
this.resetQuotasIfNeeded();
this.usage.daily.spent += actualCost;
this.usage.monthly.spent += actualCost;
this.updateUsageCalculations();
}
/**
* Get current quota usage
*/
getUsage() {
this.resetQuotasIfNeeded();
this.updateUsageCalculations();
return { ...this.usage };
}
/**
* Get recent alerts
*/
getAlerts() {
return [...this.alerts];
}
/**
* Clear alerts
*/
clearAlerts() {
this.alerts = [];
}
/**
* Update quota configuration
*/
updateConfig(config) {
this.config = { ...this.config, ...config };
this.updateUsageCalculations();
}
initializeUsage() {
const now = Date.now();
return {
daily: { spent: 0, budget: this.config.dailyBudget, remaining: this.config.dailyBudget, percentage: 0 },
monthly: { spent: 0, budget: this.config.monthlyBudget, remaining: this.config.monthlyBudget, percentage: 0 },
lastReset: { daily: now, monthly: now }
};
}
updateUsageCalculations() {
// Daily calculations
this.usage.daily.budget = this.config.dailyBudget;
this.usage.daily.remaining = Math.max(0, this.config.dailyBudget - this.usage.daily.spent);
this.usage.daily.percentage = (this.usage.daily.spent / this.config.dailyBudget) * 100;
// Monthly calculations
this.usage.monthly.budget = this.config.monthlyBudget;
this.usage.monthly.remaining = Math.max(0, this.config.monthlyBudget - this.usage.monthly.spent);
this.usage.monthly.percentage = (this.usage.monthly.spent / this.config.monthlyBudget) * 100;
}
resetQuotasIfNeeded() {
const now = Date.now();
const today = new Date(now);
const lastDaily = new Date(this.usage.lastReset.daily);
const lastMonthly = new Date(this.usage.lastReset.monthly);
// Reset daily quota (new day)
if (today.getDate() !== lastDaily.getDate() || today.getMonth() !== lastDaily.getMonth()) {
this.usage.daily.spent = 0;
this.usage.lastReset.daily = now;
}
// Reset monthly quota (new month)
if (today.getMonth() !== lastMonthly.getMonth() || today.getFullYear() !== lastMonthly.getFullYear()) {
this.usage.monthly.spent = 0;
this.usage.lastReset.monthly = now;
}
}
checkThresholds(estimatedCost) {
for (const threshold of this.config.alertThresholds.sort((a, b) => b - a)) {
// Check daily threshold
const dailyProjected = this.usage.daily.spent + estimatedCost;
const dailyPercentage = (dailyProjected / this.config.dailyBudget) * 100;
if (dailyPercentage >= threshold && this.usage.daily.percentage < threshold) {
const alert = {
type: threshold >= 95 ? 'critical' : 'warning',
period: 'daily',
threshold,
current: dailyProjected,
budget: this.config.dailyBudget,
message: `Daily spending has reached ${threshold}% of budget ($${dailyProjected.toFixed(4)} / $${this.config.dailyBudget})`
};
this.alerts.push(alert);
return alert;
}
// Check monthly threshold
const monthlyProjected = this.usage.monthly.spent + estimatedCost;
const monthlyPercentage = (monthlyProjected / this.config.monthlyBudget) * 100;
if (monthlyPercentage >= threshold && this.usage.monthly.percentage < threshold) {
const alert = {
type: threshold >= 95 ? 'critical' : 'warning',
period: 'monthly',
threshold,
current: monthlyProjected,
budget: this.config.monthlyBudget,
message: `Monthly spending has reached ${threshold}% of budget ($${monthlyProjected.toFixed(4)} / $${this.config.monthlyBudget})`
};
this.alerts.push(alert);
return alert;
}
}
return undefined;
}
}
exports.QuotaManager = QuotaManager;
//# sourceMappingURL=rate-limiting.js.map