The @agentdoor/core rate limiter provides an in-memory token bucket implementation for controlling request rates on a per-agent basis.
RateLimiter Class
A high-performance in-memory token bucket rate limiter with automatic cleanup.
import { RateLimiter } from "@agentdoor/core";
const limiter = new RateLimiter(
{ requests: 100, window: "1h" }, // Default config
60_000 // Cleanup interval (ms)
);
const result = limiter.check("agent_123");
if (!result.allowed) {
return {
error: "Rate limit exceeded",
retryAfter: result.retryAfter
};
}
Constructor
Default rate limit applied when check() is called without config.Defaults to:{ requests: 1000, window: "1h" }
How often to clean expired buckets in milliseconds.Defaults to 60000 (60 seconds). Set to 0 to disable automatic cleanup.
Methods
check
Check if a request from the given key is allowed under the rate limit. Consumes one token if allowed.
const result = limiter.check(
"agent_123",
{ requests: 100, window: "1h" }
);
if (result.allowed) {
console.log(`${result.remaining} requests remaining`);
console.log(`Limit resets at ${new Date(result.resetAt)}`);
} else {
console.log(`Rate limited. Retry in ${result.retryAfter}ms`);
}
Unique identifier (agent ID, IP address, etc.).
Rate limit configuration. Uses default if not provided.Show RateLimitConfig properties
Maximum number of requests in the window.
Time window string. Format: {number}{unit} where unit is s, m, h, or d.Examples: "1h", "30m", "1d"
Rate limit check result.Show RateLimitResult properties
Whether the request is allowed.
Remaining requests in the current window.
Total limit (maximum requests per window).
When the current window resets (unix timestamp in milliseconds).
Milliseconds until the next token is available. Only present when allowed is false.
consume
Consume multiple tokens at once (for batch or weighted requests).
const result = limiter.consume(
"agent_123",
10, // Consume 10 tokens
{ requests: 100, window: "1h" }
);
if (!result.allowed) {
console.log(`Insufficient tokens. Need 10, have ${result.remaining}`);
}
Number of tokens to consume.
Rate limit configuration. Uses default if not provided.
peek
Get the current state of a rate limit bucket without consuming tokens.
const result = limiter.peek("agent_123");
if (result) {
console.log(`Current tokens: ${result.remaining}/${result.limit}`);
} else {
console.log("No bucket exists for this key");
}
Rate limit configuration.
Current remaining tokens and limit info, or null if no bucket exists.
reset
Reset the rate limit bucket for a given key.
limiter.reset("agent_123");
// Next request from agent_123 will have a full bucket
Unique identifier to reset.
resetAll
Remove all rate limit buckets.
limiter.resetAll();
// All agents start with fresh buckets
destroy
Stop the periodic cleanup interval and clear all buckets. Call during shutdown to prevent dangling timers.
// On server shutdown:
limiter.destroy();
size (getter)
Get the number of active rate limit buckets (for monitoring).
console.log(`Tracking ${limiter.size} active agents`);
Utility Functions
parseWindow
Parse a rate limit window string into milliseconds.
import { parseWindow } from "@agentdoor/core";
const ms = parseWindow("1h"); // 3600000
const ms = parseWindow("30m"); // 1800000
const ms = parseWindow("1d"); // 86400000
Window duration string. Format: {number}{unit} where unit is:
s: seconds
m: minutes
h: hours
d: days
Duration in milliseconds.
Throws: Error if format is invalid.
Usage Examples
Basic Agent Rate Limiting
import { RateLimiter } from "@agentdoor/core";
import type { Request, Response, NextFunction } from "express";
const limiter = new RateLimiter();
// Middleware for agent rate limiting
function rateLimitMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
if (!req.agent) {
return next(); // Not authenticated
}
const result = limiter.check(
req.agent.id,
req.agent.rateLimit
);
// Set rate limit headers
res.setHeader("X-RateLimit-Limit", result.limit);
res.setHeader("X-RateLimit-Remaining", result.remaining);
res.setHeader("X-RateLimit-Reset", result.resetAt);
if (!result.allowed) {
res.setHeader("Retry-After", Math.ceil(result.retryAfter! / 1000));
return res.status(429).json({
error: "Rate limit exceeded",
retryAfter: result.retryAfter
});
}
next();
}
app.use(rateLimitMiddleware);
Per-Endpoint Rate Limiting
const globalLimiter = new RateLimiter(
{ requests: 1000, window: "1h" }
);
const registrationLimiter = new RateLimiter(
{ requests: 10, window: "1h" }
);
app.post("/agentdoor/register", (req, res) => {
const ip = req.ip;
const result = registrationLimiter.check(ip);
if (!result.allowed) {
return res.status(429).json({
error: "Too many registration attempts",
retryAfter: result.retryAfter
});
}
// Process registration...
});
Weighted Rate Limiting
const limiter = new RateLimiter(
{ requests: 100, window: "1h" }
);
// Different costs for different operations
app.post("/api/simple", (req, res) => {
const result = limiter.consume(req.agent.id, 1);
if (!result.allowed) {
return res.status(429).json({ error: "Rate limit exceeded" });
}
// Handle simple request...
});
app.post("/api/expensive", (req, res) => {
const result = limiter.consume(req.agent.id, 10);
if (!result.allowed) {
return res.status(429).json({
error: "Insufficient rate limit tokens",
required: 10,
available: result.remaining
});
}
// Handle expensive request...
});
Dynamic Rate Limits
const limiter = new RateLimiter();
app.use((req, res, next) => {
if (!req.agent) return next();
// Higher limits for premium agents
const config = req.agent.metadata.tier === "premium"
? { requests: 10000, window: "1h" }
: { requests: 1000, window: "1h" };
const result = limiter.check(req.agent.id, config);
if (!result.allowed) {
return res.status(429).json({
error: "Rate limit exceeded",
tier: req.agent.metadata.tier
});
}
next();
});
Monitoring and Alerting
const limiter = new RateLimiter();
// Monitor rate limiter usage
setInterval(() => {
const activeBuckets = limiter.size;
console.log(`Active rate limit buckets: ${activeBuckets}`);
// Alert if too many buckets (potential memory issue)
if (activeBuckets > 10000) {
console.warn("High number of active rate limit buckets!");
}
}, 60_000);
// Check current state before processing
app.post("/api/batch", async (req, res) => {
const current = limiter.peek(req.agent.id);
if (!current || current.remaining < 10) {
return res.status(429).json({
error: "Insufficient tokens for batch operation",
available: current?.remaining ?? 0,
required: 10
});
}
// Process batch...
});
Token Bucket Algorithm
The rate limiter uses a token bucket algorithm with continuous refill:
- Each key (agent) gets a bucket with a maximum capacity
- Tokens are continuously refilled based on elapsed time
- Each request consumes one token (or more with
consume())
- Requests are allowed only if sufficient tokens are available
- Buckets automatically refill to maximum capacity over time
Advantages:
- Smooth rate limiting (no sudden window resets)
- Allows burst traffic up to bucket capacity
- Memory efficient (buckets auto-cleaned when idle)
- No external dependencies required
- Memory usage: Each active key uses ~100 bytes
- Cleanup: Stale buckets are automatically removed after 2x window duration
- Precision: Uses millisecond-precision timestamps
- Thread safety: Not thread-safe; use one instance per process
For distributed rate limiting across multiple processes, use Redis or a similar external store.