agentic-flow 1.9.3 → 1.10.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +298 -0
  2. package/dist/cli-proxy.js +19 -1
  3. package/dist/core/long-running-agent.js +219 -0
  4. package/dist/core/provider-manager.js +434 -0
  5. package/dist/examples/use-provider-fallback.js +176 -0
  6. package/dist/proxy/adaptive-proxy.js +224 -0
  7. package/dist/proxy/anthropic-to-gemini.js +2 -2
  8. package/dist/proxy/http2-proxy-optimized.js +191 -0
  9. package/dist/proxy/http2-proxy.js +381 -0
  10. package/dist/proxy/http3-proxy-old.js +331 -0
  11. package/dist/proxy/http3-proxy.js +51 -0
  12. package/dist/proxy/websocket-proxy.js +406 -0
  13. package/dist/utils/auth.js +52 -0
  14. package/dist/utils/compression-middleware.js +149 -0
  15. package/dist/utils/connection-pool.js +184 -0
  16. package/dist/utils/rate-limiter.js +48 -0
  17. package/dist/utils/response-cache.js +211 -0
  18. package/dist/utils/streaming-optimizer.js +141 -0
  19. package/docs/.claude-flow/metrics/performance.json +3 -3
  20. package/docs/.claude-flow/metrics/task-metrics.json +3 -3
  21. package/docs/ISSUE-55-VALIDATION.md +152 -0
  22. package/docs/OPTIMIZATIONS.md +460 -0
  23. package/docs/README.md +217 -0
  24. package/docs/issues/ISSUE-xenova-transformers-dependency.md +380 -0
  25. package/docs/providers/LANDING-PAGE-PROVIDER-CONTENT.md +204 -0
  26. package/docs/providers/PROVIDER-FALLBACK-GUIDE.md +619 -0
  27. package/docs/providers/PROVIDER-FALLBACK-SUMMARY.md +418 -0
  28. package/package.json +1 -1
  29. package/scripts/claude +31 -0
  30. package/validation/test-gemini-exclusiveMinimum-fix.ts +142 -0
  31. package/validation/test-provider-fallback.ts +285 -0
  32. package/validation/validate-v1.10.0-docker.sh +296 -0
  33. package/wasm/reasoningbank/reasoningbank_wasm_bg.js +2 -2
  34. package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
  35. package/docs/INDEX.md +0 -279
  36. package/docs/guides/.claude-flow/metrics/agent-metrics.json +0 -1
  37. package/docs/guides/.claude-flow/metrics/performance.json +0 -9
  38. package/docs/guides/.claude-flow/metrics/task-metrics.json +0 -10
  39. package/docs/router/.claude-flow/metrics/agent-metrics.json +0 -1
  40. package/docs/router/.claude-flow/metrics/performance.json +0 -9
  41. package/docs/router/.claude-flow/metrics/task-metrics.json +0 -10
  42. /package/docs/{TEST-V1.7.8.Dockerfile → docker-tests/TEST-V1.7.8.Dockerfile} +0 -0
  43. /package/docs/{TEST-V1.7.9-NODE20.Dockerfile → docker-tests/TEST-V1.7.9-NODE20.Dockerfile} +0 -0
  44. /package/docs/{TEST-V1.7.9.Dockerfile → docker-tests/TEST-V1.7.9.Dockerfile} +0 -0
  45. /package/docs/{v1.7.1-QUICK-START.md → guides/QUICK-START-v1.7.1.md} +0 -0
  46. /package/docs/{INTEGRATION-COMPLETE.md → integration-docs/INTEGRATION-COMPLETE.md} +0 -0
  47. /package/docs/{QUIC_FINAL_STATUS.md → quic/QUIC_FINAL_STATUS.md} +0 -0
  48. /package/docs/{README_QUIC_PHASE1.md → quic/README_QUIC_PHASE1.md} +0 -0
  49. /package/docs/{AGENTDB_TESTING.md → testing/AGENTDB_TESTING.md} +0 -0
@@ -0,0 +1,381 @@
1
+ /**
2
+ * HTTP/2 Proxy for LLM Streaming
3
+ *
4
+ * Features:
5
+ * - Multiplexing: Multiple streams over single connection
6
+ * - Header compression: HPACK reduces overhead by 30-80%
7
+ * - Server push: Proactive data delivery
8
+ * - Stream prioritization: Critical responses first
9
+ * - Binary protocol: More efficient than HTTP/1.1
10
+ *
11
+ * Performance: 30-50% faster streaming latency
12
+ */
13
+ import http2 from 'http2';
14
+ import { readFileSync, existsSync } from 'fs';
15
+ import crypto from 'crypto';
16
+ import { logger } from '../utils/logger.js';
17
+ import { RateLimiter } from '../utils/rate-limiter.js';
18
+ import { AuthManager } from '../utils/auth.js';
19
+ export class HTTP2Proxy {
20
+ server;
21
+ config;
22
+ rateLimiter;
23
+ authManager;
24
+ constructor(config) {
25
+ this.config = config;
26
+ // Create secure server if certs provided, otherwise HTTP/2 cleartext
27
+ if (config.cert && config.key && existsSync(config.cert) && existsSync(config.key)) {
28
+ // Validate TLS certificates
29
+ const certData = readFileSync(config.cert);
30
+ const keyData = readFileSync(config.key);
31
+ try {
32
+ const certObj = new crypto.X509Certificate(certData);
33
+ const now = new Date();
34
+ const validTo = new Date(certObj.validTo);
35
+ if (now > validTo) {
36
+ throw new Error('TLS certificate has expired');
37
+ }
38
+ if (now < new Date(certObj.validFrom)) {
39
+ throw new Error('TLS certificate is not yet valid');
40
+ }
41
+ logger.info('TLS certificate validated', {
42
+ subject: certObj.subject,
43
+ issuer: certObj.issuer,
44
+ validFrom: certObj.validFrom,
45
+ validTo: certObj.validTo
46
+ });
47
+ }
48
+ catch (error) {
49
+ logger.error('TLS certificate validation failed', { error: error.message });
50
+ throw error;
51
+ }
52
+ this.server = http2.createSecureServer({
53
+ cert: certData,
54
+ key: keyData,
55
+ allowHTTP1: config.allowHTTP1 ?? true,
56
+ minVersion: 'TLSv1.3',
57
+ ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256'
58
+ });
59
+ logger.info('HTTP/2 secure server created', { allowHTTP1: config.allowHTTP1 });
60
+ }
61
+ else {
62
+ // HTTP/2 cleartext (h2c) - for testing/development
63
+ this.server = http2.createServer();
64
+ logger.warn('HTTP/2 running in cleartext mode (h2c) - use TLS in production');
65
+ }
66
+ // Initialize rate limiter
67
+ if (config.rateLimit) {
68
+ this.rateLimiter = new RateLimiter(config.rateLimit);
69
+ logger.info('Rate limiting enabled', config.rateLimit);
70
+ }
71
+ // Initialize authentication
72
+ this.authManager = new AuthManager(config.apiKeys);
73
+ if (this.authManager.hasKeys()) {
74
+ logger.info('API key authentication enabled');
75
+ }
76
+ this.setupRoutes();
77
+ }
78
+ setupRoutes() {
79
+ this.server.on('stream', (stream, headers) => {
80
+ const path = headers[':path'];
81
+ const method = headers[':method'];
82
+ logger.debug('HTTP/2 stream request', { path, method });
83
+ if (path === '/v1/messages' && method === 'POST') {
84
+ this.handleMessagesRequest(stream, headers);
85
+ }
86
+ else if (path === '/health') {
87
+ this.handleHealthCheck(stream);
88
+ }
89
+ else {
90
+ stream.respond({ ':status': 404 });
91
+ stream.end(JSON.stringify({ error: 'Not Found' }));
92
+ }
93
+ });
94
+ this.server.on('error', (error) => {
95
+ logger.error('HTTP/2 server error', { error: error.message });
96
+ });
97
+ }
98
+ handleHealthCheck(stream) {
99
+ stream.respond({
100
+ ':status': 200,
101
+ 'content-type': 'application/json'
102
+ });
103
+ stream.end(JSON.stringify({
104
+ status: 'ok',
105
+ service: 'http2-proxy',
106
+ protocol: 'HTTP/2'
107
+ }));
108
+ }
109
+ async handleMessagesRequest(stream, headers) {
110
+ try {
111
+ // Authentication check
112
+ if (!this.authManager.authenticate(headers)) {
113
+ stream.respond({ ':status': 401 });
114
+ stream.end(JSON.stringify({
115
+ error: {
116
+ type: 'authentication_error',
117
+ message: 'Invalid or missing API key'
118
+ }
119
+ }));
120
+ return;
121
+ }
122
+ // Rate limiting check
123
+ if (this.rateLimiter) {
124
+ const clientIp = headers['x-forwarded-for'] || 'unknown';
125
+ try {
126
+ await this.rateLimiter.consume(clientIp);
127
+ }
128
+ catch (error) {
129
+ stream.respond({ ':status': 429 });
130
+ stream.end(JSON.stringify({
131
+ error: {
132
+ type: 'rate_limit_exceeded',
133
+ message: error.message
134
+ }
135
+ }));
136
+ return;
137
+ }
138
+ }
139
+ // Read request body with size limit
140
+ const MAX_BODY_SIZE = 1024 * 1024; // 1MB
141
+ let totalSize = 0;
142
+ const chunks = [];
143
+ stream.on('data', (chunk) => {
144
+ totalSize += chunk.length;
145
+ if (totalSize > MAX_BODY_SIZE) {
146
+ stream.respond({ ':status': 413 });
147
+ stream.end(JSON.stringify({
148
+ error: {
149
+ type: 'request_too_large',
150
+ message: 'Request body exceeds 1MB limit'
151
+ }
152
+ }));
153
+ stream.destroy(new Error('Request too large'));
154
+ return;
155
+ }
156
+ chunks.push(chunk);
157
+ });
158
+ await new Promise((resolve) => stream.on('end', resolve));
159
+ const body = JSON.parse(Buffer.concat(chunks).toString());
160
+ logger.info('HTTP/2 messages request', {
161
+ model: body.model,
162
+ stream: body.stream,
163
+ messageCount: body.messages?.length
164
+ });
165
+ // Convert Anthropic format to Gemini format
166
+ const geminiReq = this.convertAnthropicToGemini(body);
167
+ // Determine endpoint based on streaming
168
+ const endpoint = body.stream ? 'streamGenerateContent' : 'generateContent';
169
+ const streamParam = body.stream ? '&alt=sse' : '';
170
+ const geminiBaseUrl = this.config.geminiBaseUrl || 'https://generativelanguage.googleapis.com/v1beta';
171
+ const url = `${geminiBaseUrl}/models/gemini-2.0-flash-exp:${endpoint}?key=${this.config.geminiApiKey}${streamParam}`;
172
+ // Forward to Gemini
173
+ const response = await fetch(url, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Content-Type': 'application/json'
177
+ },
178
+ body: JSON.stringify(geminiReq)
179
+ });
180
+ if (!response.ok) {
181
+ const error = await response.text();
182
+ logger.error('Gemini API error', { status: response.status, error });
183
+ stream.respond({ ':status': response.status });
184
+ stream.end(JSON.stringify({
185
+ error: {
186
+ type: 'api_error',
187
+ message: error
188
+ }
189
+ }));
190
+ return;
191
+ }
192
+ // Handle streaming vs non-streaming
193
+ if (body.stream) {
194
+ // Stream response using HTTP/2 multiplexing
195
+ stream.respond({
196
+ ':status': 200,
197
+ 'content-type': 'text/event-stream',
198
+ 'cache-control': 'no-cache',
199
+ 'connection': 'keep-alive'
200
+ });
201
+ const reader = response.body?.getReader();
202
+ if (!reader) {
203
+ throw new Error('No response body');
204
+ }
205
+ const decoder = new TextDecoder();
206
+ let chunkCount = 0;
207
+ while (true) {
208
+ const { done, value } = await reader.read();
209
+ if (done)
210
+ break;
211
+ const chunk = decoder.decode(value);
212
+ chunkCount++;
213
+ const anthropicChunk = this.convertGeminiStreamToAnthropic(chunk);
214
+ stream.write(anthropicChunk);
215
+ }
216
+ logger.info('HTTP/2 stream complete', { totalChunks: chunkCount });
217
+ stream.end();
218
+ }
219
+ else {
220
+ // Non-streaming response
221
+ const geminiRes = await response.json();
222
+ const anthropicRes = this.convertGeminiToAnthropic(geminiRes);
223
+ stream.respond({
224
+ ':status': 200,
225
+ 'content-type': 'application/json'
226
+ });
227
+ stream.end(JSON.stringify(anthropicRes));
228
+ }
229
+ }
230
+ catch (error) {
231
+ logger.error('HTTP/2 request error', { error: error.message });
232
+ stream.respond({ ':status': 500 });
233
+ stream.end(JSON.stringify({
234
+ error: {
235
+ type: 'proxy_error',
236
+ message: error.message
237
+ }
238
+ }));
239
+ }
240
+ }
241
+ convertAnthropicToGemini(anthropicReq) {
242
+ const contents = [];
243
+ let systemPrefix = '';
244
+ if (anthropicReq.system) {
245
+ systemPrefix = `System: ${anthropicReq.system}\n\n`;
246
+ }
247
+ for (let i = 0; i < anthropicReq.messages.length; i++) {
248
+ const msg = anthropicReq.messages[i];
249
+ let text;
250
+ if (typeof msg.content === 'string') {
251
+ text = msg.content;
252
+ }
253
+ else if (Array.isArray(msg.content)) {
254
+ text = msg.content
255
+ .filter((block) => block.type === 'text')
256
+ .map((block) => block.text)
257
+ .join('\n');
258
+ }
259
+ else {
260
+ text = '';
261
+ }
262
+ if (i === 0 && msg.role === 'user' && systemPrefix) {
263
+ text = systemPrefix + text;
264
+ }
265
+ contents.push({
266
+ role: msg.role === 'assistant' ? 'model' : 'user',
267
+ parts: [{ text }]
268
+ });
269
+ }
270
+ const geminiReq = { contents };
271
+ if (anthropicReq.temperature !== undefined || anthropicReq.max_tokens !== undefined) {
272
+ geminiReq.generationConfig = {};
273
+ if (anthropicReq.temperature !== undefined) {
274
+ geminiReq.generationConfig.temperature = anthropicReq.temperature;
275
+ }
276
+ if (anthropicReq.max_tokens !== undefined) {
277
+ geminiReq.generationConfig.maxOutputTokens = anthropicReq.max_tokens;
278
+ }
279
+ }
280
+ return geminiReq;
281
+ }
282
+ convertGeminiStreamToAnthropic(chunk) {
283
+ const lines = chunk.split('\n').filter(line => line.trim());
284
+ const anthropicChunks = [];
285
+ for (const line of lines) {
286
+ try {
287
+ if (line.startsWith('data: ')) {
288
+ const jsonStr = line.substring(6);
289
+ const parsed = JSON.parse(jsonStr);
290
+ const candidate = parsed.candidates?.[0];
291
+ const text = candidate?.content?.parts?.[0]?.text;
292
+ if (text) {
293
+ anthropicChunks.push(`event: content_block_delta\ndata: ${JSON.stringify({
294
+ type: 'content_block_delta',
295
+ delta: { type: 'text_delta', text }
296
+ })}\n\n`);
297
+ }
298
+ if (candidate?.finishReason) {
299
+ anthropicChunks.push('event: message_stop\ndata: {}\n\n');
300
+ }
301
+ }
302
+ }
303
+ catch (e) {
304
+ logger.debug('Failed to parse stream chunk', { line });
305
+ }
306
+ }
307
+ return anthropicChunks.join('');
308
+ }
309
+ convertGeminiToAnthropic(geminiRes) {
310
+ const candidate = geminiRes.candidates?.[0];
311
+ if (!candidate) {
312
+ throw new Error('No candidates in Gemini response');
313
+ }
314
+ const content = candidate.content;
315
+ const parts = content?.parts || [];
316
+ let rawText = '';
317
+ for (const part of parts) {
318
+ if (part.text) {
319
+ rawText += part.text;
320
+ }
321
+ }
322
+ return {
323
+ id: `msg_${Date.now()}`,
324
+ type: 'message',
325
+ role: 'assistant',
326
+ model: 'gemini-2.0-flash-exp',
327
+ content: [
328
+ {
329
+ type: 'text',
330
+ text: rawText
331
+ }
332
+ ],
333
+ stop_reason: 'end_turn',
334
+ usage: {
335
+ input_tokens: geminiRes.usageMetadata?.promptTokenCount || 0,
336
+ output_tokens: geminiRes.usageMetadata?.candidatesTokenCount || 0
337
+ }
338
+ };
339
+ }
340
+ start() {
341
+ return new Promise((resolve) => {
342
+ this.server.listen(this.config.port, () => {
343
+ const protocol = this.config.cert ? 'https' : 'http';
344
+ logger.info('HTTP/2 proxy started', {
345
+ port: this.config.port,
346
+ protocol,
347
+ url: `${protocol}://localhost:${this.config.port}`
348
+ });
349
+ console.log(`\n✅ HTTP/2 Proxy running at ${protocol}://localhost:${this.config.port}`);
350
+ console.log(` Protocol: HTTP/2 (30-50% faster streaming)`);
351
+ console.log(` Features: Multiplexing, Header Compression, Stream Prioritization\n`);
352
+ resolve();
353
+ });
354
+ });
355
+ }
356
+ stop() {
357
+ return new Promise((resolve) => {
358
+ this.server.close(() => {
359
+ logger.info('HTTP/2 proxy stopped');
360
+ resolve();
361
+ });
362
+ });
363
+ }
364
+ }
365
+ // CLI entry point
366
+ if (import.meta.url === `file://${process.argv[1]}`) {
367
+ const port = parseInt(process.env.PORT || '3001');
368
+ const geminiApiKey = process.env.GOOGLE_GEMINI_API_KEY;
369
+ if (!geminiApiKey) {
370
+ console.error('❌ Error: GOOGLE_GEMINI_API_KEY environment variable required');
371
+ process.exit(1);
372
+ }
373
+ const proxy = new HTTP2Proxy({
374
+ port,
375
+ geminiApiKey,
376
+ cert: process.env.TLS_CERT,
377
+ key: process.env.TLS_KEY,
378
+ geminiBaseUrl: process.env.GEMINI_BASE_URL
379
+ });
380
+ proxy.start();
381
+ }