agentic-flow 1.9.4 → 1.10.1

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/dist/proxy/adaptive-proxy.js +224 -0
  3. package/dist/proxy/anthropic-to-gemini.js +2 -2
  4. package/dist/proxy/http2-proxy-optimized.js +191 -0
  5. package/dist/proxy/http2-proxy.js +381 -0
  6. package/dist/proxy/http3-proxy-old.js +331 -0
  7. package/dist/proxy/http3-proxy.js +51 -0
  8. package/dist/proxy/websocket-proxy.js +406 -0
  9. package/dist/utils/adaptive-pool-sizing.js +414 -0
  10. package/dist/utils/auth.js +52 -0
  11. package/dist/utils/circular-rate-limiter.js +391 -0
  12. package/dist/utils/compression-middleware.js +149 -0
  13. package/dist/utils/connection-pool.js +184 -0
  14. package/dist/utils/dynamic-compression.js +298 -0
  15. package/dist/utils/http2-multiplexing.js +319 -0
  16. package/dist/utils/lazy-auth.js +311 -0
  17. package/dist/utils/rate-limiter.js +48 -0
  18. package/dist/utils/response-cache.js +211 -0
  19. package/dist/utils/server-push.js +251 -0
  20. package/dist/utils/streaming-optimizer.js +141 -0
  21. package/dist/utils/zero-copy-buffer.js +286 -0
  22. package/docs/.claude-flow/metrics/performance.json +3 -3
  23. package/docs/.claude-flow/metrics/task-metrics.json +3 -3
  24. package/docs/DOCKER-VERIFICATION.md +207 -0
  25. package/docs/ISSUE-55-VALIDATION.md +171 -0
  26. package/docs/NPX_AGENTDB_SETUP.md +175 -0
  27. package/docs/OPTIMIZATIONS.md +460 -0
  28. package/docs/PHASE2-IMPLEMENTATION-SUMMARY.md +275 -0
  29. package/docs/PHASE2-PHASE3-COMPLETE-SUMMARY.md +453 -0
  30. package/docs/PHASE3-IMPLEMENTATION-SUMMARY.md +357 -0
  31. package/docs/PUBLISH_GUIDE.md +438 -0
  32. package/docs/README.md +217 -0
  33. package/docs/RELEASE-v1.10.0-COMPLETE.md +382 -0
  34. package/docs/archive/.agentdb-instructions.md +66 -0
  35. package/docs/archive/AGENT-BOOSTER-STATUS.md +292 -0
  36. package/docs/archive/CHANGELOG-v1.3.0.md +120 -0
  37. package/docs/archive/COMPLETION_REPORT_v1.7.1.md +335 -0
  38. package/docs/archive/IMPLEMENTATION_SUMMARY_v1.7.1.md +241 -0
  39. package/docs/archive/SUPABASE-INTEGRATION-COMPLETE.md +357 -0
  40. package/docs/archive/TESTING_QUICK_START.md +223 -0
  41. package/docs/archive/TOOL-EMULATION-INTEGRATION-ISSUE.md +669 -0
  42. package/docs/archive/VALIDATION_v1.7.1.md +234 -0
  43. package/docs/issues/ISSUE-xenova-transformers-dependency.md +380 -0
  44. package/docs/releases/PUBLISH_CHECKLIST_v1.10.0.md +396 -0
  45. package/docs/releases/PUBLISH_SUMMARY_v1.7.1.md +198 -0
  46. package/docs/releases/RELEASE_NOTES_v1.10.0.md +464 -0
  47. package/docs/releases/RELEASE_NOTES_v1.7.0.md +297 -0
  48. package/docs/releases/RELEASE_v1.7.1.md +327 -0
  49. package/package.json +1 -1
  50. package/scripts/claude +31 -0
  51. package/validation/docker-npm-validation.sh +170 -0
  52. package/validation/simple-npm-validation.sh +131 -0
  53. package/validation/test-gemini-exclusiveMinimum-fix.ts +142 -0
  54. package/validation/test-gemini-models.ts +200 -0
  55. package/validation/validate-v1.10.0-docker.sh +296 -0
  56. package/wasm/reasoningbank/reasoningbank_wasm_bg.js +2 -2
  57. package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
  58. package/docs/INDEX.md +0 -279
  59. package/docs/guides/.claude-flow/metrics/agent-metrics.json +0 -1
  60. package/docs/guides/.claude-flow/metrics/performance.json +0 -9
  61. package/docs/guides/.claude-flow/metrics/task-metrics.json +0 -10
  62. package/docs/router/.claude-flow/metrics/agent-metrics.json +0 -1
  63. package/docs/router/.claude-flow/metrics/performance.json +0 -9
  64. package/docs/router/.claude-flow/metrics/task-metrics.json +0 -10
  65. /package/docs/{TEST-V1.7.8.Dockerfile → docker-tests/TEST-V1.7.8.Dockerfile} +0 -0
  66. /package/docs/{TEST-V1.7.9-NODE20.Dockerfile → docker-tests/TEST-V1.7.9-NODE20.Dockerfile} +0 -0
  67. /package/docs/{TEST-V1.7.9.Dockerfile → docker-tests/TEST-V1.7.9.Dockerfile} +0 -0
  68. /package/docs/{v1.7.1-QUICK-START.md → guides/QUICK-START-v1.7.1.md} +0 -0
  69. /package/docs/{INTEGRATION-COMPLETE.md → integration-docs/INTEGRATION-COMPLETE.md} +0 -0
  70. /package/docs/{LANDING-PAGE-PROVIDER-CONTENT.md → providers/LANDING-PAGE-PROVIDER-CONTENT.md} +0 -0
  71. /package/docs/{PROVIDER-FALLBACK-GUIDE.md → providers/PROVIDER-FALLBACK-GUIDE.md} +0 -0
  72. /package/docs/{PROVIDER-FALLBACK-SUMMARY.md → providers/PROVIDER-FALLBACK-SUMMARY.md} +0 -0
  73. /package/docs/{QUIC_FINAL_STATUS.md → quic/QUIC_FINAL_STATUS.md} +0 -0
  74. /package/docs/{README_QUIC_PHASE1.md → quic/README_QUIC_PHASE1.md} +0 -0
  75. /package/docs/{AGENTDB_TESTING.md → testing/AGENTDB_TESTING.md} +0 -0
@@ -0,0 +1,406 @@
1
+ /**
2
+ * WebSocket Proxy for LLM Streaming
3
+ *
4
+ * Features:
5
+ * - Bidirectional: Full-duplex communication
6
+ * - Mobile-friendly: Better for unstable connections
7
+ * - Lower overhead: No HTTP headers per message
8
+ * - Reconnection: Automatic retry on disconnect
9
+ * - Universal support: Works everywhere (browsers, mobile, desktop)
10
+ *
11
+ * Use Case: Fallback for unreliable connections (mobile, poor WiFi)
12
+ */
13
+ import { WebSocketServer } from 'ws';
14
+ import { createServer } from 'http';
15
+ import { logger } from '../utils/logger.js';
16
+ export class WebSocketProxy {
17
+ wss;
18
+ server;
19
+ config;
20
+ clients = new Map();
21
+ pingInterval;
22
+ activeConnections = 0;
23
+ constructor(config) {
24
+ this.config = config;
25
+ this.server = createServer();
26
+ this.wss = new WebSocketServer({ server: this.server });
27
+ this.setupHandlers();
28
+ this.setupHeartbeat();
29
+ logger.info('WebSocket proxy created', {
30
+ port: config.port,
31
+ pingInterval: config.pingInterval || 30000
32
+ });
33
+ }
34
+ setupHandlers() {
35
+ this.wss.on('connection', (ws, req) => {
36
+ const clientIp = req.socket.remoteAddress;
37
+ // Check connection limit (DoS protection)
38
+ const maxConnections = this.config.maxConnections || 1000;
39
+ if (this.activeConnections >= maxConnections) {
40
+ logger.warn('Connection limit reached', {
41
+ activeConnections: this.activeConnections,
42
+ maxConnections
43
+ });
44
+ ws.close(1008, 'Server at capacity');
45
+ return;
46
+ }
47
+ this.activeConnections++;
48
+ logger.info('WebSocket client connected', {
49
+ clientIp,
50
+ activeConnections: this.activeConnections
51
+ });
52
+ // Initialize client state
53
+ this.clients.set(ws, {
54
+ ws,
55
+ isAlive: true,
56
+ lastPing: new Date(),
57
+ requests: 0
58
+ });
59
+ // Handle incoming messages
60
+ ws.on('message', async (data) => {
61
+ const client = this.clients.get(ws);
62
+ if (!client)
63
+ return;
64
+ client.requests++;
65
+ try {
66
+ const message = JSON.parse(data.toString());
67
+ logger.debug('WebSocket message received', { type: message.type });
68
+ if (message.type === 'ping') {
69
+ // Respond to ping
70
+ ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
71
+ client.isAlive = true;
72
+ client.lastPing = new Date();
73
+ }
74
+ else if (message.type === 'streaming_request') {
75
+ // Handle LLM streaming request
76
+ await this.handleStreamingRequest(ws, message.data);
77
+ }
78
+ else if (message.type === 'non_streaming_request') {
79
+ // Handle non-streaming request
80
+ await this.handleNonStreamingRequest(ws, message.data);
81
+ }
82
+ else {
83
+ ws.send(JSON.stringify({
84
+ type: 'error',
85
+ error: `Unknown message type: ${message.type}`
86
+ }));
87
+ }
88
+ }
89
+ catch (error) {
90
+ logger.error('WebSocket message error', { error: error.message });
91
+ ws.send(JSON.stringify({
92
+ type: 'error',
93
+ error: error.message
94
+ }));
95
+ }
96
+ });
97
+ // Handle pong responses
98
+ ws.on('pong', () => {
99
+ const client = this.clients.get(ws);
100
+ if (client) {
101
+ client.isAlive = true;
102
+ client.lastPing = new Date();
103
+ }
104
+ });
105
+ // Set connection timeout
106
+ const connectionTimeout = this.config.connectionTimeout || 300000; // 5 minutes
107
+ const timeoutHandle = setTimeout(() => {
108
+ logger.warn('Connection timeout', { clientIp });
109
+ ws.close(1000, 'Connection timeout');
110
+ }, connectionTimeout);
111
+ // Handle close
112
+ ws.on('close', () => {
113
+ logger.info('WebSocket client disconnected', { clientIp });
114
+ this.clients.delete(ws);
115
+ this.activeConnections--;
116
+ clearTimeout(timeoutHandle);
117
+ });
118
+ // Handle errors
119
+ ws.on('error', (error) => {
120
+ logger.error('WebSocket error', { clientIp, error: error.message });
121
+ this.clients.delete(ws);
122
+ this.activeConnections--;
123
+ });
124
+ // Send initial handshake
125
+ ws.send(JSON.stringify({
126
+ type: 'connected',
127
+ protocols: ['anthropic-messages-v1'],
128
+ features: ['streaming', 'non-streaming', 'ping-pong']
129
+ }));
130
+ });
131
+ this.wss.on('error', (error) => {
132
+ logger.error('WebSocket server error', { error: error.message });
133
+ });
134
+ }
135
+ setupHeartbeat() {
136
+ const interval = this.config.pingInterval || 30000; // 30 seconds
137
+ const timeout = this.config.pingTimeout || 60000; // 60 seconds
138
+ this.pingInterval = setInterval(() => {
139
+ const now = Date.now();
140
+ this.clients.forEach((client, ws) => {
141
+ // Check if client responded to last ping
142
+ if (!client.isAlive) {
143
+ const timeSinceLastPing = now - client.lastPing.getTime();
144
+ if (timeSinceLastPing > timeout) {
145
+ logger.warn('Client did not respond to ping, terminating', {
146
+ timeSinceLastPing
147
+ });
148
+ ws.terminate();
149
+ this.clients.delete(ws);
150
+ return;
151
+ }
152
+ }
153
+ // Send ping
154
+ client.isAlive = false;
155
+ ws.ping();
156
+ });
157
+ }, interval);
158
+ }
159
+ async handleStreamingRequest(ws, anthropicReq) {
160
+ try {
161
+ logger.info('WebSocket streaming request', {
162
+ model: anthropicReq.model,
163
+ messageCount: anthropicReq.messages?.length
164
+ });
165
+ // Convert to Gemini format
166
+ const geminiReq = this.convertAnthropicToGemini(anthropicReq);
167
+ // Send streaming start event
168
+ ws.send(JSON.stringify({
169
+ type: 'message_start',
170
+ message: {
171
+ id: `msg_${Date.now()}`,
172
+ role: 'assistant',
173
+ model: anthropicReq.model || 'gemini-2.0-flash-exp'
174
+ }
175
+ }));
176
+ // Forward to Gemini
177
+ const geminiBaseUrl = this.config.geminiBaseUrl || 'https://generativelanguage.googleapis.com/v1beta';
178
+ const url = `${geminiBaseUrl}/models/gemini-2.0-flash-exp:streamGenerateContent?key=${this.config.geminiApiKey}&alt=sse`;
179
+ const response = await fetch(url, {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/json' },
182
+ body: JSON.stringify(geminiReq)
183
+ });
184
+ if (!response.ok) {
185
+ const error = await response.text();
186
+ throw new Error(`Gemini API error: ${error}`);
187
+ }
188
+ const reader = response.body?.getReader();
189
+ if (!reader) {
190
+ throw new Error('No response body');
191
+ }
192
+ const decoder = new TextDecoder();
193
+ let chunkCount = 0;
194
+ while (true) {
195
+ const { done, value } = await reader.read();
196
+ if (done)
197
+ break;
198
+ const chunk = decoder.decode(value);
199
+ chunkCount++;
200
+ // Parse Gemini SSE and send as WebSocket messages
201
+ const lines = chunk.split('\n').filter(line => line.trim());
202
+ for (const line of lines) {
203
+ if (line.startsWith('data: ')) {
204
+ try {
205
+ const jsonStr = line.substring(6);
206
+ const parsed = JSON.parse(jsonStr);
207
+ const candidate = parsed.candidates?.[0];
208
+ const text = candidate?.content?.parts?.[0]?.text;
209
+ if (text) {
210
+ // Send text delta
211
+ ws.send(JSON.stringify({
212
+ type: 'content_block_delta',
213
+ delta: { type: 'text_delta', text }
214
+ }));
215
+ }
216
+ if (candidate?.finishReason) {
217
+ // Send completion
218
+ ws.send(JSON.stringify({
219
+ type: 'message_stop',
220
+ stop_reason: 'end_turn'
221
+ }));
222
+ }
223
+ }
224
+ catch (e) {
225
+ logger.debug('Failed to parse stream chunk', { line });
226
+ }
227
+ }
228
+ }
229
+ }
230
+ logger.info('WebSocket stream complete', { totalChunks: chunkCount });
231
+ }
232
+ catch (error) {
233
+ logger.error('WebSocket streaming error', { error: error.message });
234
+ ws.send(JSON.stringify({
235
+ type: 'error',
236
+ error: error.message
237
+ }));
238
+ }
239
+ }
240
+ async handleNonStreamingRequest(ws, anthropicReq) {
241
+ try {
242
+ logger.info('WebSocket non-streaming request', {
243
+ model: anthropicReq.model,
244
+ messageCount: anthropicReq.messages?.length
245
+ });
246
+ // Convert to Gemini format
247
+ const geminiReq = this.convertAnthropicToGemini(anthropicReq);
248
+ // Forward to Gemini
249
+ const geminiBaseUrl = this.config.geminiBaseUrl || 'https://generativelanguage.googleapis.com/v1beta';
250
+ const url = `${geminiBaseUrl}/models/gemini-2.0-flash-exp:generateContent?key=${this.config.geminiApiKey}`;
251
+ const response = await fetch(url, {
252
+ method: 'POST',
253
+ headers: { 'Content-Type': 'application/json' },
254
+ body: JSON.stringify(geminiReq)
255
+ });
256
+ if (!response.ok) {
257
+ const error = await response.text();
258
+ throw new Error(`Gemini API error: ${error}`);
259
+ }
260
+ const geminiRes = await response.json();
261
+ const anthropicRes = this.convertGeminiToAnthropic(geminiRes);
262
+ // Send complete response
263
+ ws.send(JSON.stringify({
264
+ type: 'message_complete',
265
+ message: anthropicRes
266
+ }));
267
+ }
268
+ catch (error) {
269
+ logger.error('WebSocket non-streaming error', { error: error.message });
270
+ ws.send(JSON.stringify({
271
+ type: 'error',
272
+ error: error.message
273
+ }));
274
+ }
275
+ }
276
+ convertAnthropicToGemini(anthropicReq) {
277
+ const contents = [];
278
+ let systemPrefix = '';
279
+ if (anthropicReq.system) {
280
+ systemPrefix = `System: ${anthropicReq.system}\n\n`;
281
+ }
282
+ for (let i = 0; i < anthropicReq.messages.length; i++) {
283
+ const msg = anthropicReq.messages[i];
284
+ let text;
285
+ if (typeof msg.content === 'string') {
286
+ text = msg.content;
287
+ }
288
+ else if (Array.isArray(msg.content)) {
289
+ text = msg.content
290
+ .filter((block) => block.type === 'text')
291
+ .map((block) => block.text)
292
+ .join('\n');
293
+ }
294
+ else {
295
+ text = '';
296
+ }
297
+ if (i === 0 && msg.role === 'user' && systemPrefix) {
298
+ text = systemPrefix + text;
299
+ }
300
+ contents.push({
301
+ role: msg.role === 'assistant' ? 'model' : 'user',
302
+ parts: [{ text }]
303
+ });
304
+ }
305
+ const geminiReq = { contents };
306
+ if (anthropicReq.temperature !== undefined || anthropicReq.max_tokens !== undefined) {
307
+ geminiReq.generationConfig = {};
308
+ if (anthropicReq.temperature !== undefined) {
309
+ geminiReq.generationConfig.temperature = anthropicReq.temperature;
310
+ }
311
+ if (anthropicReq.max_tokens !== undefined) {
312
+ geminiReq.generationConfig.maxOutputTokens = anthropicReq.max_tokens;
313
+ }
314
+ }
315
+ return geminiReq;
316
+ }
317
+ convertGeminiToAnthropic(geminiRes) {
318
+ const candidate = geminiRes.candidates?.[0];
319
+ if (!candidate) {
320
+ throw new Error('No candidates in Gemini response');
321
+ }
322
+ const content = candidate.content;
323
+ const parts = content?.parts || [];
324
+ let rawText = '';
325
+ for (const part of parts) {
326
+ if (part.text) {
327
+ rawText += part.text;
328
+ }
329
+ }
330
+ return {
331
+ id: `msg_${Date.now()}`,
332
+ type: 'message',
333
+ role: 'assistant',
334
+ model: 'gemini-2.0-flash-exp',
335
+ content: [
336
+ {
337
+ type: 'text',
338
+ text: rawText
339
+ }
340
+ ],
341
+ stop_reason: 'end_turn',
342
+ usage: {
343
+ input_tokens: geminiRes.usageMetadata?.promptTokenCount || 0,
344
+ output_tokens: geminiRes.usageMetadata?.candidatesTokenCount || 0
345
+ }
346
+ };
347
+ }
348
+ start() {
349
+ return new Promise((resolve) => {
350
+ this.server.listen(this.config.port, () => {
351
+ logger.info('WebSocket proxy started', {
352
+ port: this.config.port,
353
+ url: `ws://localhost:${this.config.port}`
354
+ });
355
+ console.log(`\n✅ WebSocket Proxy running at ws://localhost:${this.config.port}`);
356
+ console.log(` Protocol: WebSocket (fallback for unreliable connections)`);
357
+ console.log(` Features: Bidirectional, Mobile-friendly, Auto-reconnect\n`);
358
+ resolve();
359
+ });
360
+ });
361
+ }
362
+ stop() {
363
+ return new Promise((resolve) => {
364
+ // Clear ping interval
365
+ if (this.pingInterval) {
366
+ clearInterval(this.pingInterval);
367
+ }
368
+ // Close all client connections
369
+ this.clients.forEach((client, ws) => {
370
+ ws.close(1000, 'Server shutting down');
371
+ });
372
+ this.clients.clear();
373
+ // Close WebSocket server
374
+ this.wss.close(() => {
375
+ // Close HTTP server
376
+ this.server.close(() => {
377
+ logger.info('WebSocket proxy stopped');
378
+ resolve();
379
+ });
380
+ });
381
+ });
382
+ }
383
+ }
384
+ // CLI entry point
385
+ if (import.meta.url === `file://${process.argv[1]}`) {
386
+ const port = parseInt(process.env.PORT || '8080');
387
+ const geminiApiKey = process.env.GOOGLE_GEMINI_API_KEY;
388
+ if (!geminiApiKey) {
389
+ console.error('❌ Error: GOOGLE_GEMINI_API_KEY environment variable required');
390
+ process.exit(1);
391
+ }
392
+ const proxy = new WebSocketProxy({
393
+ port,
394
+ geminiApiKey,
395
+ geminiBaseUrl: process.env.GEMINI_BASE_URL,
396
+ pingInterval: 30000,
397
+ pingTimeout: 60000
398
+ });
399
+ proxy.start();
400
+ // Graceful shutdown
401
+ process.on('SIGINT', async () => {
402
+ console.log('\n🛑 Shutting down WebSocket proxy...');
403
+ await proxy.stop();
404
+ process.exit(0);
405
+ });
406
+ }