claude-flow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load balancer and rate limiting for MCP
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MCPLoadBalancerConfig, MCPRequest, MCPResponse, MCPSession } from '../utils/types.ts';
|
|
6
|
+
import { ILogger } from '../core/logger.ts';
|
|
7
|
+
import { MCPError } from '../utils/errors.ts';
|
|
8
|
+
|
|
9
|
+
export interface RequestMetrics {
|
|
10
|
+
requestId: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
method: string;
|
|
13
|
+
startTime: number;
|
|
14
|
+
endTime?: number;
|
|
15
|
+
success?: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LoadBalancerMetrics {
|
|
20
|
+
totalRequests: number;
|
|
21
|
+
successfulRequests: number;
|
|
22
|
+
failedRequests: number;
|
|
23
|
+
rateLimitedRequests: number;
|
|
24
|
+
averageResponseTime: number;
|
|
25
|
+
requestsPerSecond: number;
|
|
26
|
+
circuitBreakerTrips: number;
|
|
27
|
+
lastReset: Date;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ILoadBalancer {
|
|
31
|
+
shouldAllowRequest(session: MCPSession, request: MCPRequest): Promise<boolean>;
|
|
32
|
+
recordRequestStart(session: MCPSession, request: MCPRequest): RequestMetrics;
|
|
33
|
+
recordRequestEnd(metrics: RequestMetrics, response?: MCPResponse, error?: Error): void;
|
|
34
|
+
getMetrics(): LoadBalancerMetrics;
|
|
35
|
+
resetMetrics(): void;
|
|
36
|
+
isCircuitBreakerOpen(): boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Circuit breaker state
|
|
41
|
+
*/
|
|
42
|
+
enum CircuitBreakerState {
|
|
43
|
+
CLOSED = 'closed',
|
|
44
|
+
OPEN = 'open',
|
|
45
|
+
HALF_OPEN = 'half_open',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Rate limiter using token bucket algorithm
|
|
50
|
+
*/
|
|
51
|
+
class RateLimiter {
|
|
52
|
+
private tokens: number;
|
|
53
|
+
private lastRefill: number;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
private maxTokens: number,
|
|
57
|
+
private refillRate: number, // tokens per second
|
|
58
|
+
) {
|
|
59
|
+
this.tokens = maxTokens;
|
|
60
|
+
this.lastRefill = Date.now();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
tryConsume(tokens = 1): boolean {
|
|
64
|
+
this.refill();
|
|
65
|
+
|
|
66
|
+
if (this.tokens >= tokens) {
|
|
67
|
+
this.tokens -= tokens;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private refill(): void {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const timePassed = (now - this.lastRefill) / 1000;
|
|
77
|
+
const tokensToAdd = Math.floor(timePassed * this.refillRate);
|
|
78
|
+
|
|
79
|
+
if (tokensToAdd > 0) {
|
|
80
|
+
this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
|
|
81
|
+
this.lastRefill = now;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getTokens(): number {
|
|
86
|
+
this.refill();
|
|
87
|
+
return this.tokens;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Circuit breaker implementation
|
|
93
|
+
*/
|
|
94
|
+
class CircuitBreaker {
|
|
95
|
+
private state = CircuitBreakerState.CLOSED;
|
|
96
|
+
private failureCount = 0;
|
|
97
|
+
private lastFailureTime = 0;
|
|
98
|
+
private successCount = 0;
|
|
99
|
+
|
|
100
|
+
constructor(
|
|
101
|
+
private failureThreshold: number,
|
|
102
|
+
private recoveryTimeout: number, // milliseconds
|
|
103
|
+
private halfOpenMaxRequests = 3,
|
|
104
|
+
) {}
|
|
105
|
+
|
|
106
|
+
canExecute(): boolean {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
|
|
109
|
+
switch (this.state) {
|
|
110
|
+
case CircuitBreakerState.CLOSED:
|
|
111
|
+
return true;
|
|
112
|
+
|
|
113
|
+
case CircuitBreakerState.OPEN:
|
|
114
|
+
if (now - this.lastFailureTime >= this.recoveryTimeout) {
|
|
115
|
+
this.state = CircuitBreakerState.HALF_OPEN;
|
|
116
|
+
this.successCount = 0;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
|
|
121
|
+
case CircuitBreakerState.HALF_OPEN:
|
|
122
|
+
return this.successCount < this.halfOpenMaxRequests;
|
|
123
|
+
|
|
124
|
+
default:
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
recordSuccess(): void {
|
|
130
|
+
if (this.state === CircuitBreakerState.HALF_OPEN) {
|
|
131
|
+
this.successCount++;
|
|
132
|
+
if (this.successCount >= this.halfOpenMaxRequests) {
|
|
133
|
+
this.state = CircuitBreakerState.CLOSED;
|
|
134
|
+
this.failureCount = 0;
|
|
135
|
+
}
|
|
136
|
+
} else if (this.state === CircuitBreakerState.CLOSED) {
|
|
137
|
+
this.failureCount = 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
recordFailure(): void {
|
|
142
|
+
this.failureCount++;
|
|
143
|
+
this.lastFailureTime = Date.now();
|
|
144
|
+
|
|
145
|
+
if (this.state === CircuitBreakerState.HALF_OPEN) {
|
|
146
|
+
this.state = CircuitBreakerState.OPEN;
|
|
147
|
+
} else if (this.state === CircuitBreakerState.CLOSED && this.failureCount >= this.failureThreshold) {
|
|
148
|
+
this.state = CircuitBreakerState.OPEN;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getState(): CircuitBreakerState {
|
|
153
|
+
return this.state;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getMetrics(): { state: string; failureCount: number; successCount: number } {
|
|
157
|
+
return {
|
|
158
|
+
state: this.state,
|
|
159
|
+
failureCount: this.failureCount,
|
|
160
|
+
successCount: this.successCount,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Load balancer implementation
|
|
167
|
+
*/
|
|
168
|
+
export class LoadBalancer implements ILoadBalancer {
|
|
169
|
+
private rateLimiter: RateLimiter;
|
|
170
|
+
private circuitBreaker: CircuitBreaker;
|
|
171
|
+
private sessionRateLimiters = new Map<string, RateLimiter>();
|
|
172
|
+
private metrics: LoadBalancerMetrics;
|
|
173
|
+
private requestTimes: number[] = [];
|
|
174
|
+
private requestsInLastSecond = 0;
|
|
175
|
+
private lastSecondTimestamp = 0;
|
|
176
|
+
|
|
177
|
+
constructor(
|
|
178
|
+
private config: MCPLoadBalancerConfig,
|
|
179
|
+
private logger: ILogger,
|
|
180
|
+
) {
|
|
181
|
+
this.rateLimiter = new RateLimiter(
|
|
182
|
+
config.maxRequestsPerSecond,
|
|
183
|
+
config.maxRequestsPerSecond,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
this.circuitBreaker = new CircuitBreaker(
|
|
187
|
+
config.circuitBreakerThreshold,
|
|
188
|
+
30000, // 30 second recovery timeout
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
this.metrics = {
|
|
192
|
+
totalRequests: 0,
|
|
193
|
+
successfulRequests: 0,
|
|
194
|
+
failedRequests: 0,
|
|
195
|
+
rateLimitedRequests: 0,
|
|
196
|
+
averageResponseTime: 0,
|
|
197
|
+
requestsPerSecond: 0,
|
|
198
|
+
circuitBreakerTrips: 0,
|
|
199
|
+
lastReset: new Date(),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Clean up old session rate limiters periodically
|
|
203
|
+
setInterval(() => {
|
|
204
|
+
this.cleanupSessionRateLimiters();
|
|
205
|
+
}, 300000); // Every 5 minutes
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async shouldAllowRequest(session: MCPSession, request: MCPRequest): Promise<boolean> {
|
|
209
|
+
if (!this.config.enabled) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check circuit breaker
|
|
214
|
+
if (!this.circuitBreaker.canExecute()) {
|
|
215
|
+
this.logger.warn('Request rejected by circuit breaker', {
|
|
216
|
+
sessionId: session.id,
|
|
217
|
+
method: request.method,
|
|
218
|
+
circuitState: this.circuitBreaker.getState(),
|
|
219
|
+
});
|
|
220
|
+
this.metrics.circuitBreakerTrips++;
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check global rate limit
|
|
225
|
+
if (!this.rateLimiter.tryConsume()) {
|
|
226
|
+
this.logger.warn('Request rejected by global rate limiter', {
|
|
227
|
+
sessionId: session.id,
|
|
228
|
+
method: request.method,
|
|
229
|
+
remainingTokens: this.rateLimiter.getTokens(),
|
|
230
|
+
});
|
|
231
|
+
this.metrics.rateLimitedRequests++;
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check per-session rate limit
|
|
236
|
+
const sessionRateLimiter = this.getSessionRateLimiter(session.id);
|
|
237
|
+
if (!sessionRateLimiter.tryConsume()) {
|
|
238
|
+
this.logger.warn('Request rejected by session rate limiter', {
|
|
239
|
+
sessionId: session.id,
|
|
240
|
+
method: request.method,
|
|
241
|
+
remainingTokens: sessionRateLimiter.getTokens(),
|
|
242
|
+
});
|
|
243
|
+
this.metrics.rateLimitedRequests++;
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
recordRequestStart(session: MCPSession, request: MCPRequest): RequestMetrics {
|
|
251
|
+
const requestMetrics: RequestMetrics = {
|
|
252
|
+
requestId: request.id.toString(),
|
|
253
|
+
sessionId: session.id,
|
|
254
|
+
method: request.method,
|
|
255
|
+
startTime: Date.now(),
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
this.metrics.totalRequests++;
|
|
259
|
+
this.updateRequestsPerSecond();
|
|
260
|
+
|
|
261
|
+
this.logger.debug('Request started', {
|
|
262
|
+
requestId: requestMetrics.requestId,
|
|
263
|
+
sessionId: session.id,
|
|
264
|
+
method: request.method,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return requestMetrics;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
recordRequestEnd(metrics: RequestMetrics, response?: MCPResponse, error?: Error): void {
|
|
271
|
+
metrics.endTime = Date.now();
|
|
272
|
+
const duration = metrics.endTime - metrics.startTime;
|
|
273
|
+
|
|
274
|
+
// Update response time tracking
|
|
275
|
+
this.requestTimes.push(duration);
|
|
276
|
+
if (this.requestTimes.length > 1000) {
|
|
277
|
+
this.requestTimes.shift(); // Keep only last 1000 requests
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const success = !error && (!response || !response.error);
|
|
281
|
+
metrics.success = success;
|
|
282
|
+
const errorMessage = error?.message || response?.error?.message;
|
|
283
|
+
if (errorMessage) {
|
|
284
|
+
metrics.error = errorMessage;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (success) {
|
|
288
|
+
this.metrics.successfulRequests++;
|
|
289
|
+
this.circuitBreaker.recordSuccess();
|
|
290
|
+
} else {
|
|
291
|
+
this.metrics.failedRequests++;
|
|
292
|
+
this.circuitBreaker.recordFailure();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Update average response time
|
|
296
|
+
this.metrics.averageResponseTime = this.calculateAverageResponseTime();
|
|
297
|
+
|
|
298
|
+
this.logger.debug('Request completed', {
|
|
299
|
+
requestId: metrics.requestId,
|
|
300
|
+
sessionId: metrics.sessionId,
|
|
301
|
+
method: metrics.method,
|
|
302
|
+
duration,
|
|
303
|
+
success,
|
|
304
|
+
error: metrics.error,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getMetrics(): LoadBalancerMetrics {
|
|
309
|
+
return { ...this.metrics };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
resetMetrics(): void {
|
|
313
|
+
this.metrics = {
|
|
314
|
+
totalRequests: 0,
|
|
315
|
+
successfulRequests: 0,
|
|
316
|
+
failedRequests: 0,
|
|
317
|
+
rateLimitedRequests: 0,
|
|
318
|
+
averageResponseTime: 0,
|
|
319
|
+
requestsPerSecond: 0,
|
|
320
|
+
circuitBreakerTrips: 0,
|
|
321
|
+
lastReset: new Date(),
|
|
322
|
+
};
|
|
323
|
+
this.requestTimes = [];
|
|
324
|
+
|
|
325
|
+
this.logger.info('Load balancer metrics reset');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
isCircuitBreakerOpen(): boolean {
|
|
329
|
+
return this.circuitBreaker.getState() === CircuitBreakerState.OPEN;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
getDetailedMetrics(): {
|
|
333
|
+
loadBalancer: LoadBalancerMetrics;
|
|
334
|
+
circuitBreaker: { state: string; failureCount: number; successCount: number };
|
|
335
|
+
rateLimiter: { tokens: number; maxTokens: number };
|
|
336
|
+
sessions: number;
|
|
337
|
+
} {
|
|
338
|
+
return {
|
|
339
|
+
loadBalancer: this.getMetrics(),
|
|
340
|
+
circuitBreaker: this.circuitBreaker.getMetrics(),
|
|
341
|
+
rateLimiter: {
|
|
342
|
+
tokens: this.rateLimiter.getTokens(),
|
|
343
|
+
maxTokens: this.config.maxRequestsPerSecond,
|
|
344
|
+
},
|
|
345
|
+
sessions: this.sessionRateLimiters.size,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private getSessionRateLimiter(sessionId: string): RateLimiter {
|
|
350
|
+
let rateLimiter = this.sessionRateLimiters.get(sessionId);
|
|
351
|
+
|
|
352
|
+
if (!rateLimiter) {
|
|
353
|
+
// Create a per-session rate limiter (more restrictive than global)
|
|
354
|
+
const sessionLimit = Math.max(1, Math.floor(this.config.maxRequestsPerSecond / 10));
|
|
355
|
+
rateLimiter = new RateLimiter(sessionLimit, sessionLimit);
|
|
356
|
+
this.sessionRateLimiters.set(sessionId, rateLimiter);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return rateLimiter;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private calculateAverageResponseTime(): number {
|
|
363
|
+
if (this.requestTimes.length === 0) {
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const sum = this.requestTimes.reduce((acc, time) => acc + time, 0);
|
|
368
|
+
return sum / this.requestTimes.length;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private updateRequestsPerSecond(): void {
|
|
372
|
+
const now = Math.floor(Date.now() / 1000);
|
|
373
|
+
|
|
374
|
+
if (now !== this.lastSecondTimestamp) {
|
|
375
|
+
this.metrics.requestsPerSecond = this.requestsInLastSecond;
|
|
376
|
+
this.requestsInLastSecond = 1;
|
|
377
|
+
this.lastSecondTimestamp = now;
|
|
378
|
+
} else {
|
|
379
|
+
this.requestsInLastSecond++;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private cleanupSessionRateLimiters(): void {
|
|
384
|
+
// Remove rate limiters for sessions that haven't been used recently
|
|
385
|
+
const cutoffTime = Date.now() - 300000; // 5 minutes ago
|
|
386
|
+
let cleaned = 0;
|
|
387
|
+
|
|
388
|
+
for (const [sessionId, rateLimiter] of this.sessionRateLimiters.entries()) {
|
|
389
|
+
// If the rate limiter has full tokens, it hasn't been used recently
|
|
390
|
+
if (rateLimiter.getTokens() === this.config.maxRequestsPerSecond) {
|
|
391
|
+
this.sessionRateLimiters.delete(sessionId);
|
|
392
|
+
cleaned++;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (cleaned > 0) {
|
|
397
|
+
this.logger.debug('Cleaned up session rate limiters', { count: cleaned });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Request queue for handling backpressure
|
|
404
|
+
*/
|
|
405
|
+
export class RequestQueue {
|
|
406
|
+
private queue: Array<{
|
|
407
|
+
session: MCPSession;
|
|
408
|
+
request: MCPRequest;
|
|
409
|
+
resolve: (result: any) => void;
|
|
410
|
+
reject: (error: Error) => void;
|
|
411
|
+
timestamp: number;
|
|
412
|
+
}> = [];
|
|
413
|
+
|
|
414
|
+
private processing = false;
|
|
415
|
+
private maxQueueSize: number;
|
|
416
|
+
private requestTimeout: number;
|
|
417
|
+
|
|
418
|
+
constructor(
|
|
419
|
+
maxQueueSize = 1000,
|
|
420
|
+
requestTimeout = 30000, // 30 seconds
|
|
421
|
+
private logger: ILogger,
|
|
422
|
+
) {
|
|
423
|
+
this.maxQueueSize = maxQueueSize;
|
|
424
|
+
this.requestTimeout = requestTimeout;
|
|
425
|
+
|
|
426
|
+
// Clean up expired requests periodically
|
|
427
|
+
setInterval(() => {
|
|
428
|
+
this.cleanupExpiredRequests();
|
|
429
|
+
}, 10000); // Every 10 seconds
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async enqueue<T>(
|
|
433
|
+
session: MCPSession,
|
|
434
|
+
request: MCPRequest,
|
|
435
|
+
processor: (session: MCPSession, request: MCPRequest) => Promise<T>,
|
|
436
|
+
): Promise<T> {
|
|
437
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
438
|
+
throw new MCPError('Request queue is full');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return new Promise<T>((resolve, reject) => {
|
|
442
|
+
this.queue.push({
|
|
443
|
+
session,
|
|
444
|
+
request,
|
|
445
|
+
resolve,
|
|
446
|
+
reject,
|
|
447
|
+
timestamp: Date.now(),
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
if (!this.processing) {
|
|
451
|
+
this.processQueue(processor);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private async processQueue<T>(
|
|
457
|
+
processor: (session: MCPSession, request: MCPRequest) => Promise<T>,
|
|
458
|
+
): Promise<void> {
|
|
459
|
+
if (this.processing) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
this.processing = true;
|
|
464
|
+
|
|
465
|
+
while (this.queue.length > 0) {
|
|
466
|
+
const item = this.queue.shift()!;
|
|
467
|
+
|
|
468
|
+
// Check if request has expired
|
|
469
|
+
if (Date.now() - item.timestamp > this.requestTimeout) {
|
|
470
|
+
item.reject(new MCPError('Request timeout'));
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
const result = await processor(item.session, item.request);
|
|
476
|
+
item.resolve(result);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
item.reject(error instanceof Error ? error : new Error(String(error)));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
this.processing = false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private cleanupExpiredRequests(): void {
|
|
486
|
+
const now = Date.now();
|
|
487
|
+
let cleaned = 0;
|
|
488
|
+
|
|
489
|
+
this.queue = this.queue.filter((item) => {
|
|
490
|
+
if (now - item.timestamp > this.requestTimeout) {
|
|
491
|
+
item.reject(new MCPError('Request timeout'));
|
|
492
|
+
cleaned++;
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
return true;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
if (cleaned > 0) {
|
|
499
|
+
this.logger.warn('Cleaned up expired requests from queue', { count: cleaned });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
getQueueSize(): number {
|
|
504
|
+
return this.queue.length;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
isProcessing(): boolean {
|
|
508
|
+
return this.processing;
|
|
509
|
+
}
|
|
510
|
+
}
|