amiralek commited on
Commit
e38e8c2
·
verified ·
1 Parent(s): 519d830

Add public demo rate limiting

Browse files
Files changed (1) hide show
  1. src/mastra/index.ts +96 -1
src/mastra/index.ts CHANGED
@@ -9,6 +9,48 @@ import {
9
  } from '@mastra/observability';
10
  import { sqlAgent } from './agents/sql-agent';
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  export const mastra = new Mastra({
13
  agents: { sqlAgent },
14
  storage: new LibSQLStore({
@@ -28,4 +70,57 @@ export const mastra = new Mastra({
28
  },
29
  },
30
  }),
31
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  } from '@mastra/observability';
10
  import { sqlAgent } from './agents/sql-agent';
11
 
12
+ const RATE_LIMIT_WINDOW_MS = 10 * 60 * 1000;
13
+ const MAX_AGENT_REQUESTS_PER_WINDOW = 10;
14
+
15
+ type RateLimitEntry = {
16
+ count: number;
17
+ resetAt: number;
18
+ };
19
+
20
+ const agentRequestLimits = new Map<string, RateLimitEntry>();
21
+
22
+ function getClientIdentifier(c: {
23
+ req: {
24
+ header: (name: string) => string | undefined;
25
+ };
26
+ }): string {
27
+ const forwardedFor = c.req.header('x-forwarded-for');
28
+
29
+ if (forwardedFor) {
30
+ return forwardedFor.split(',')[0].trim();
31
+ }
32
+
33
+ return c.req.header('x-real-ip') ?? 'unknown-client';
34
+ }
35
+
36
+ function isPaidAgentRequest(method: string, path: string): boolean {
37
+ if (method !== 'POST') {
38
+ return false;
39
+ }
40
+
41
+ return /^\/api\/agents\/[^/]+\/(generate|stream|stream-until-idle|send-message)$/.test(
42
+ path,
43
+ );
44
+ }
45
+
46
+ function cleanExpiredLimits(now: number): void {
47
+ for (const [key, entry] of agentRequestLimits.entries()) {
48
+ if (entry.resetAt <= now) {
49
+ agentRequestLimits.delete(key);
50
+ }
51
+ }
52
+ }
53
+
54
  export const mastra = new Mastra({
55
  agents: { sqlAgent },
56
  storage: new LibSQLStore({
 
70
  },
71
  },
72
  }),
73
+ server: {
74
+ bodySizeLimit: 32 * 1024,
75
+ middleware: [
76
+ {
77
+ path: '/api/agents/*',
78
+ handler: async (c, next) => {
79
+ const method = c.req.method;
80
+ const path = new URL(c.req.url).pathname;
81
+
82
+ if (!isPaidAgentRequest(method, path)) {
83
+ await next();
84
+ return;
85
+ }
86
+
87
+ const now = Date.now();
88
+ cleanExpiredLimits(now);
89
+
90
+ const clientId = getClientIdentifier(c);
91
+ const current = agentRequestLimits.get(clientId);
92
+
93
+ if (!current || current.resetAt <= now) {
94
+ agentRequestLimits.set(clientId, {
95
+ count: 1,
96
+ resetAt: now + RATE_LIMIT_WINDOW_MS,
97
+ });
98
+
99
+ await next();
100
+ return;
101
+ }
102
+
103
+ if (current.count >= MAX_AGENT_REQUESTS_PER_WINDOW) {
104
+ const retryAfterSeconds = Math.ceil((current.resetAt - now) / 1000);
105
+
106
+ return c.json(
107
+ {
108
+ error: 'Public demo rate limit reached. Please try again later.',
109
+ retryAfterSeconds,
110
+ },
111
+ 429,
112
+ {
113
+ 'Retry-After': String(retryAfterSeconds),
114
+ },
115
+ );
116
+ }
117
+
118
+ current.count += 1;
119
+ agentRequestLimits.set(clientId, current);
120
+
121
+ await next();
122
+ },
123
+ },
124
+ ],
125
+ },
126
+ });