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,434 @@
1
+ /**
2
+ * Provider Manager - Intelligent LLM Provider Fallback & Dynamic Switching
3
+ *
4
+ * Features:
5
+ * - Automatic failover between providers
6
+ * - Health monitoring and circuit breaking
7
+ * - Cost-based optimization
8
+ * - Performance tracking
9
+ * - Rate limit handling
10
+ * - Provider quality scoring
11
+ */
12
+ import { logger } from '../utils/logger.js';
13
+ export class ProviderManager {
14
+ providers = new Map();
15
+ health = new Map();
16
+ metrics = new Map();
17
+ strategy;
18
+ currentProvider = null;
19
+ healthCheckIntervals = new Map();
20
+ constructor(providers, strategy = {
21
+ type: 'priority',
22
+ maxFailures: 3,
23
+ recoveryTime: 60000, // 1 minute
24
+ retryBackoff: 'exponential'
25
+ }) {
26
+ this.strategy = strategy;
27
+ this.initializeProviders(providers);
28
+ this.startHealthChecks();
29
+ }
30
+ /**
31
+ * Initialize provider configurations
32
+ */
33
+ initializeProviders(providers) {
34
+ for (const provider of providers) {
35
+ this.providers.set(provider.name, provider);
36
+ // Initialize health tracking
37
+ this.health.set(provider.name, {
38
+ provider: provider.name,
39
+ isHealthy: true,
40
+ lastChecked: new Date(),
41
+ consecutiveFailures: 0,
42
+ averageLatency: 0,
43
+ successRate: 1.0,
44
+ errorRate: 0.0,
45
+ circuitBreakerOpen: false
46
+ });
47
+ // Initialize metrics
48
+ this.metrics.set(provider.name, {
49
+ provider: provider.name,
50
+ totalRequests: 0,
51
+ successfulRequests: 0,
52
+ failedRequests: 0,
53
+ totalTokens: 0,
54
+ totalCost: 0,
55
+ averageLatency: 0,
56
+ lastUsed: new Date()
57
+ });
58
+ }
59
+ logger.info('Provider Manager initialized', {
60
+ providers: Array.from(this.providers.keys()),
61
+ strategy: this.strategy.type
62
+ });
63
+ }
64
+ /**
65
+ * Start health check monitoring for all providers
66
+ */
67
+ startHealthChecks() {
68
+ for (const [name, config] of this.providers.entries()) {
69
+ if (config.healthCheckInterval) {
70
+ const interval = setInterval(() => {
71
+ this.performHealthCheck(name);
72
+ }, config.healthCheckInterval);
73
+ this.healthCheckIntervals.set(name, interval);
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Perform health check on a provider
79
+ */
80
+ async performHealthCheck(provider) {
81
+ const health = this.health.get(provider);
82
+ if (!health)
83
+ return;
84
+ try {
85
+ const startTime = Date.now();
86
+ // TODO: Implement actual health check API call
87
+ // For now, just check if circuit breaker should recover
88
+ const latency = Date.now() - startTime;
89
+ health.lastChecked = new Date();
90
+ health.averageLatency = (health.averageLatency + latency) / 2;
91
+ // Recovery logic: reset circuit breaker after recovery time
92
+ if (health.circuitBreakerOpen) {
93
+ const timeSinceLastCheck = Date.now() - health.lastChecked.getTime();
94
+ if (timeSinceLastCheck >= this.strategy.recoveryTime) {
95
+ logger.info('Circuit breaker recovering', { provider });
96
+ health.circuitBreakerOpen = false;
97
+ health.consecutiveFailures = 0;
98
+ }
99
+ }
100
+ logger.debug('Health check completed', { provider, health });
101
+ }
102
+ catch (error) {
103
+ logger.error('Health check failed', { provider, error: error.message });
104
+ this.recordFailure(provider, error);
105
+ }
106
+ }
107
+ /**
108
+ * Select the best provider based on strategy
109
+ */
110
+ async selectProvider(taskComplexity, estimatedTokens) {
111
+ const availableProviders = this.getAvailableProviders();
112
+ if (availableProviders.length === 0) {
113
+ throw new Error('No healthy providers available');
114
+ }
115
+ let selectedProvider;
116
+ switch (this.strategy.type) {
117
+ case 'priority':
118
+ selectedProvider = this.selectByPriority(availableProviders);
119
+ break;
120
+ case 'cost-optimized':
121
+ selectedProvider = this.selectByCost(availableProviders, estimatedTokens);
122
+ break;
123
+ case 'performance-optimized':
124
+ selectedProvider = this.selectByPerformance(availableProviders);
125
+ break;
126
+ case 'round-robin':
127
+ selectedProvider = this.selectRoundRobin(availableProviders);
128
+ break;
129
+ default:
130
+ selectedProvider = availableProviders[0];
131
+ }
132
+ // Apply task complexity heuristics
133
+ if (taskComplexity === 'complex' && this.providers.has('anthropic')) {
134
+ const anthropicHealth = this.health.get('anthropic');
135
+ if (anthropicHealth?.isHealthy && !anthropicHealth.circuitBreakerOpen) {
136
+ selectedProvider = 'anthropic'; // Prefer Claude for complex tasks
137
+ }
138
+ }
139
+ else if (taskComplexity === 'simple' && this.providers.has('gemini')) {
140
+ const geminiHealth = this.health.get('gemini');
141
+ if (geminiHealth?.isHealthy && !geminiHealth.circuitBreakerOpen) {
142
+ selectedProvider = 'gemini'; // Prefer Gemini for simple tasks (faster, cheaper)
143
+ }
144
+ }
145
+ this.currentProvider = selectedProvider;
146
+ logger.info('Provider selected', {
147
+ provider: selectedProvider,
148
+ strategy: this.strategy.type,
149
+ taskComplexity,
150
+ estimatedTokens
151
+ });
152
+ return selectedProvider;
153
+ }
154
+ /**
155
+ * Get list of healthy, available providers
156
+ */
157
+ getAvailableProviders() {
158
+ const available = [];
159
+ for (const [name, config] of this.providers.entries()) {
160
+ const health = this.health.get(name);
161
+ if (config.enabled &&
162
+ health?.isHealthy &&
163
+ !health.circuitBreakerOpen) {
164
+ available.push(name);
165
+ }
166
+ }
167
+ // Sort by priority
168
+ available.sort((a, b) => {
169
+ const priorityA = this.providers.get(a)?.priority ?? 999;
170
+ const priorityB = this.providers.get(b)?.priority ?? 999;
171
+ return priorityA - priorityB;
172
+ });
173
+ return available;
174
+ }
175
+ /**
176
+ * Select provider by priority
177
+ */
178
+ selectByPriority(providers) {
179
+ return providers[0]; // Already sorted by priority
180
+ }
181
+ /**
182
+ * Select provider by cost optimization
183
+ */
184
+ selectByCost(providers, estimatedTokens) {
185
+ if (!estimatedTokens) {
186
+ return this.selectByPriority(providers);
187
+ }
188
+ let bestProvider = providers[0];
189
+ let lowestCost = Infinity;
190
+ for (const provider of providers) {
191
+ const config = this.providers.get(provider);
192
+ if (!config)
193
+ continue;
194
+ const estimatedCost = (estimatedTokens / 1_000_000) * config.costPerToken;
195
+ if (estimatedCost < lowestCost) {
196
+ lowestCost = estimatedCost;
197
+ bestProvider = provider;
198
+ }
199
+ }
200
+ return bestProvider;
201
+ }
202
+ /**
203
+ * Select provider by performance (latency + success rate)
204
+ */
205
+ selectByPerformance(providers) {
206
+ let bestProvider = providers[0];
207
+ let bestScore = -Infinity;
208
+ for (const provider of providers) {
209
+ const health = this.health.get(provider);
210
+ if (!health)
211
+ continue;
212
+ // Score = success rate (weighted 70%) - normalized latency (weighted 30%)
213
+ const normalizedLatency = health.averageLatency / 1000; // Convert to seconds
214
+ const score = (health.successRate * 0.7) - (normalizedLatency * 0.3);
215
+ if (score > bestScore) {
216
+ bestScore = score;
217
+ bestProvider = provider;
218
+ }
219
+ }
220
+ return bestProvider;
221
+ }
222
+ /**
223
+ * Select provider using round-robin
224
+ */
225
+ selectRoundRobin(providers) {
226
+ if (!this.currentProvider || !providers.includes(this.currentProvider)) {
227
+ return providers[0];
228
+ }
229
+ const currentIndex = providers.indexOf(this.currentProvider);
230
+ const nextIndex = (currentIndex + 1) % providers.length;
231
+ return providers[nextIndex];
232
+ }
233
+ /**
234
+ * Execute request with automatic fallback
235
+ */
236
+ async executeWithFallback(requestFn, taskComplexity, estimatedTokens) {
237
+ const startTime = Date.now();
238
+ let attempts = 0;
239
+ let lastError = null;
240
+ // Try each available provider
241
+ const availableProviders = this.getAvailableProviders();
242
+ const provider = await this.selectProvider(taskComplexity, estimatedTokens);
243
+ // Start with selected provider, then try fallbacks
244
+ const providersToTry = [
245
+ provider,
246
+ ...availableProviders.filter(p => p !== provider)
247
+ ];
248
+ for (const currentProvider of providersToTry) {
249
+ attempts++;
250
+ const config = this.providers.get(currentProvider);
251
+ if (!config)
252
+ continue;
253
+ try {
254
+ logger.info('Attempting request', { provider: currentProvider, attempt: attempts });
255
+ const requestStartTime = Date.now();
256
+ const result = await this.executeWithRetry(requestFn, currentProvider, config.maxRetries);
257
+ const latency = Date.now() - requestStartTime;
258
+ // Record success
259
+ this.recordSuccess(currentProvider, latency, estimatedTokens || 0);
260
+ logger.info('Request successful', {
261
+ provider: currentProvider,
262
+ attempts,
263
+ latency,
264
+ totalTime: Date.now() - startTime
265
+ });
266
+ return { result, provider: currentProvider, attempts };
267
+ }
268
+ catch (error) {
269
+ lastError = error;
270
+ logger.warn('Provider request failed', {
271
+ provider: currentProvider,
272
+ attempt: attempts,
273
+ error: lastError.message
274
+ });
275
+ // Record failure
276
+ this.recordFailure(currentProvider, lastError);
277
+ // Continue to next provider
278
+ continue;
279
+ }
280
+ }
281
+ // All providers failed
282
+ throw new Error(`All providers failed after ${attempts} attempts. Last error: ${lastError?.message}`);
283
+ }
284
+ /**
285
+ * Execute request with retry logic
286
+ */
287
+ async executeWithRetry(requestFn, provider, maxRetries) {
288
+ let lastError = null;
289
+ for (let retry = 0; retry <= maxRetries; retry++) {
290
+ try {
291
+ // Apply backoff delay
292
+ if (retry > 0) {
293
+ const delay = this.calculateBackoffDelay(retry);
294
+ logger.debug('Retry backoff', { provider, retry, delay });
295
+ await new Promise(resolve => setTimeout(resolve, delay));
296
+ }
297
+ return await requestFn(provider);
298
+ }
299
+ catch (error) {
300
+ lastError = error;
301
+ // Check if error is retryable
302
+ if (!this.isRetryableError(error)) {
303
+ throw error;
304
+ }
305
+ logger.debug('Retryable error, continuing', {
306
+ provider,
307
+ retry,
308
+ error: lastError.message
309
+ });
310
+ }
311
+ }
312
+ throw lastError || new Error('Max retries exceeded');
313
+ }
314
+ /**
315
+ * Calculate backoff delay
316
+ */
317
+ calculateBackoffDelay(retry) {
318
+ if (this.strategy.retryBackoff === 'exponential') {
319
+ // Exponential backoff: 1s, 2s, 4s, 8s, 16s...
320
+ return Math.min(1000 * Math.pow(2, retry), 30000); // Max 30s
321
+ }
322
+ else {
323
+ // Linear backoff: 1s, 2s, 3s, 4s...
324
+ return Math.min(1000 * retry, 10000); // Max 10s
325
+ }
326
+ }
327
+ /**
328
+ * Check if error is retryable
329
+ */
330
+ isRetryableError(error) {
331
+ const retryablePatterns = [
332
+ /rate limit/i,
333
+ /timeout/i,
334
+ /connection/i,
335
+ /network/i,
336
+ /503/,
337
+ /502/,
338
+ /429/
339
+ ];
340
+ return retryablePatterns.some(pattern => pattern.test(error.message));
341
+ }
342
+ /**
343
+ * Record successful request
344
+ */
345
+ recordSuccess(provider, latency, tokens) {
346
+ const metrics = this.metrics.get(provider);
347
+ const health = this.health.get(provider);
348
+ const config = this.providers.get(provider);
349
+ if (metrics) {
350
+ metrics.totalRequests++;
351
+ metrics.successfulRequests++;
352
+ metrics.totalTokens += tokens;
353
+ metrics.totalCost += (tokens / 1_000_000) * (config?.costPerToken || 0);
354
+ metrics.averageLatency = (metrics.averageLatency + latency) / 2;
355
+ metrics.lastUsed = new Date();
356
+ }
357
+ if (health) {
358
+ health.consecutiveFailures = 0;
359
+ health.isHealthy = true;
360
+ health.circuitBreakerOpen = false;
361
+ health.successRate = metrics ? metrics.successfulRequests / metrics.totalRequests : 1.0;
362
+ health.errorRate = 1.0 - health.successRate;
363
+ health.averageLatency = latency;
364
+ }
365
+ }
366
+ /**
367
+ * Record failed request
368
+ */
369
+ recordFailure(provider, error) {
370
+ const metrics = this.metrics.get(provider);
371
+ const health = this.health.get(provider);
372
+ if (metrics) {
373
+ metrics.totalRequests++;
374
+ metrics.failedRequests++;
375
+ }
376
+ if (health) {
377
+ health.consecutiveFailures++;
378
+ health.successRate = metrics ? metrics.successfulRequests / metrics.totalRequests : 0.0;
379
+ health.errorRate = 1.0 - health.successRate;
380
+ // Open circuit breaker if threshold exceeded
381
+ if (health.consecutiveFailures >= this.strategy.maxFailures) {
382
+ logger.warn('Circuit breaker opened', {
383
+ provider,
384
+ consecutiveFailures: health.consecutiveFailures,
385
+ threshold: this.strategy.maxFailures
386
+ });
387
+ health.circuitBreakerOpen = true;
388
+ health.isHealthy = false;
389
+ }
390
+ }
391
+ logger.error('Provider failure recorded', {
392
+ provider,
393
+ error: error.message,
394
+ consecutiveFailures: health?.consecutiveFailures,
395
+ circuitBreakerOpen: health?.circuitBreakerOpen
396
+ });
397
+ }
398
+ /**
399
+ * Get current metrics for all providers
400
+ */
401
+ getMetrics() {
402
+ return Array.from(this.metrics.values());
403
+ }
404
+ /**
405
+ * Get health status for all providers
406
+ */
407
+ getHealth() {
408
+ return Array.from(this.health.values());
409
+ }
410
+ /**
411
+ * Get cost summary
412
+ */
413
+ getCostSummary() {
414
+ let total = 0;
415
+ let totalTokens = 0;
416
+ const byProvider = {};
417
+ for (const [name, metrics] of this.metrics.entries()) {
418
+ total += metrics.totalCost;
419
+ totalTokens += metrics.totalTokens;
420
+ byProvider[name] = metrics.totalCost;
421
+ }
422
+ return { total, byProvider: byProvider, totalTokens };
423
+ }
424
+ /**
425
+ * Cleanup and stop health checks
426
+ */
427
+ destroy() {
428
+ for (const interval of this.healthCheckIntervals.values()) {
429
+ clearInterval(interval);
430
+ }
431
+ this.healthCheckIntervals.clear();
432
+ logger.info('Provider Manager destroyed');
433
+ }
434
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Example: Long-Running Agent with Provider Fallback
3
+ *
4
+ * This example demonstrates:
5
+ * - Automatic provider fallback
6
+ * - Cost optimization
7
+ * - Health monitoring
8
+ * - Checkpointing for crash recovery
9
+ * - Budget constraints
10
+ */
11
+ import { LongRunningAgent } from '../core/long-running-agent.js';
12
+ async function main() {
13
+ console.log('🚀 Starting Long-Running Agent with Provider Fallback\n');
14
+ // Configure providers with priorities and costs
15
+ const providers = [
16
+ {
17
+ name: 'gemini',
18
+ apiKey: process.env.GOOGLE_GEMINI_API_KEY,
19
+ priority: 1, // Try Gemini first (fastest, cheapest)
20
+ maxRetries: 3,
21
+ timeout: 30000,
22
+ costPerToken: 0.00015, // $0.15 per 1M tokens
23
+ enabled: !!process.env.GOOGLE_GEMINI_API_KEY,
24
+ healthCheckInterval: 60000 // Check every minute
25
+ },
26
+ {
27
+ name: 'anthropic',
28
+ apiKey: process.env.ANTHROPIC_API_KEY,
29
+ priority: 2, // Fallback to Claude (higher quality)
30
+ maxRetries: 3,
31
+ timeout: 60000,
32
+ costPerToken: 0.003, // $3 per 1M tokens (Sonnet)
33
+ enabled: !!process.env.ANTHROPIC_API_KEY,
34
+ healthCheckInterval: 60000
35
+ },
36
+ {
37
+ name: 'openrouter',
38
+ apiKey: process.env.OPENROUTER_API_KEY,
39
+ priority: 3, // Fallback to OpenRouter
40
+ maxRetries: 3,
41
+ timeout: 60000,
42
+ costPerToken: 0.001, // Varies by model
43
+ enabled: !!process.env.OPENROUTER_API_KEY,
44
+ healthCheckInterval: 60000
45
+ },
46
+ {
47
+ name: 'onnx',
48
+ priority: 4, // Last resort (local, free but slower)
49
+ maxRetries: 2,
50
+ timeout: 120000,
51
+ costPerToken: 0, // FREE
52
+ enabled: true,
53
+ healthCheckInterval: 300000 // Check every 5 minutes
54
+ }
55
+ ];
56
+ // Create agent with cost and performance optimization
57
+ const agent = new LongRunningAgent({
58
+ agentName: 'research-and-code-agent',
59
+ providers,
60
+ fallbackStrategy: {
61
+ type: 'cost-optimized', // Prefer cheaper providers
62
+ maxFailures: 3, // Open circuit breaker after 3 failures
63
+ recoveryTime: 60000, // Try recovery after 1 minute
64
+ retryBackoff: 'exponential',
65
+ costThreshold: 0.50, // Max $0.50 per request
66
+ latencyThreshold: 30000 // Max 30s per request
67
+ },
68
+ checkpointInterval: 30000, // Save state every 30 seconds
69
+ maxRuntime: 3600000, // Max 1 hour
70
+ costBudget: 5.00 // Max $5 total spend
71
+ });
72
+ await agent.start();
73
+ try {
74
+ // Simulate long-running workflow with different task complexities
75
+ console.log('\n📋 Task 1: Simple Code Generation (Gemini optimal)\n');
76
+ const task1 = await agent.executeTask({
77
+ name: 'generate-hello-world',
78
+ complexity: 'simple',
79
+ estimatedTokens: 200,
80
+ execute: async (provider) => {
81
+ console.log(` Using provider: ${provider}`);
82
+ // Simulated API call
83
+ await new Promise(resolve => setTimeout(resolve, 1000));
84
+ return { code: 'console.log("Hello World");', provider };
85
+ }
86
+ });
87
+ console.log(` ✅ Result:`, task1);
88
+ console.log('\n📋 Task 2: Complex Architecture Design (Claude optimal)\n');
89
+ const task2 = await agent.executeTask({
90
+ name: 'design-microservices-architecture',
91
+ complexity: 'complex',
92
+ estimatedTokens: 5000,
93
+ execute: async (provider) => {
94
+ console.log(` Using provider: ${provider}`);
95
+ await new Promise(resolve => setTimeout(resolve, 2000));
96
+ return {
97
+ architecture: 'Event-driven microservices with CQRS',
98
+ provider
99
+ };
100
+ }
101
+ });
102
+ console.log(` ✅ Result:`, task2);
103
+ console.log('\n📋 Task 3: Medium Refactoring (Auto-optimized)\n');
104
+ const task3 = await agent.executeTask({
105
+ name: 'refactor-legacy-code',
106
+ complexity: 'medium',
107
+ estimatedTokens: 1500,
108
+ execute: async (provider) => {
109
+ console.log(` Using provider: ${provider}`);
110
+ await new Promise(resolve => setTimeout(resolve, 1500));
111
+ return {
112
+ refactored: true,
113
+ improvements: ['Better naming', 'Modular design'],
114
+ provider
115
+ };
116
+ }
117
+ });
118
+ console.log(` ✅ Result:`, task3);
119
+ // Simulate provider failure for demonstration
120
+ console.log('\n📋 Task 4: Testing Fallback (Simulated Failure)\n');
121
+ let failureCount = 0;
122
+ const task4 = await agent.executeTask({
123
+ name: 'test-fallback',
124
+ complexity: 'simple',
125
+ estimatedTokens: 300,
126
+ execute: async (provider) => {
127
+ console.log(` Attempting with provider: ${provider}`);
128
+ // Simulate failure on first two attempts
129
+ if (failureCount < 2) {
130
+ failureCount++;
131
+ throw new Error('Simulated rate limit error');
132
+ }
133
+ await new Promise(resolve => setTimeout(resolve, 1000));
134
+ return {
135
+ message: 'Success after fallback!',
136
+ provider,
137
+ attempts: failureCount + 1
138
+ };
139
+ }
140
+ });
141
+ console.log(` ✅ Result:`, task4);
142
+ // Get final status
143
+ console.log('\n📊 Final Agent Status:\n');
144
+ const status = agent.getStatus();
145
+ console.log(JSON.stringify(status, null, 2));
146
+ console.log('\n💰 Cost Summary:\n');
147
+ const metrics = agent.getMetrics();
148
+ console.log('Total Cost:', `$${metrics.costs.total.toFixed(4)}`);
149
+ console.log('Total Tokens:', metrics.costs.totalTokens.toLocaleString());
150
+ console.log('\nBy Provider:');
151
+ for (const [provider, cost] of Object.entries(metrics.costs.byProvider)) {
152
+ console.log(` ${provider}: $${cost.toFixed(4)}`);
153
+ }
154
+ console.log('\n📈 Provider Health:\n');
155
+ for (const health of metrics.health) {
156
+ console.log(`${health.provider}:`);
157
+ console.log(` Healthy: ${health.isHealthy}`);
158
+ console.log(` Success Rate: ${(health.successRate * 100).toFixed(1)}%`);
159
+ console.log(` Avg Latency: ${health.averageLatency.toFixed(0)}ms`);
160
+ console.log(` Circuit Breaker: ${health.circuitBreakerOpen ? 'OPEN' : 'CLOSED'}`);
161
+ console.log('');
162
+ }
163
+ }
164
+ catch (error) {
165
+ console.error('❌ Agent execution failed:', error);
166
+ }
167
+ finally {
168
+ await agent.stop();
169
+ console.log('\n✅ Agent stopped successfully\n');
170
+ }
171
+ }
172
+ // Run example
173
+ if (import.meta.url === `file://${process.argv[1]}`) {
174
+ main().catch(console.error);
175
+ }
176
+ export { main as runProviderFallbackExample };