agentic-flow 1.9.2 → 1.9.4

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/CHANGELOG.md CHANGED
@@ -5,6 +5,92 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.4] - 2025-11-06
9
+
10
+ ### Added - Enterprise Provider Fallback & Dynamic Switching 🚀
11
+
12
+ **Production-grade provider fallback for long-running agents**
13
+
14
+ #### New Core Classes
15
+
16
+ 1. **`ProviderManager`** (src/core/provider-manager.ts)
17
+ - Intelligent multi-provider management with automatic failover
18
+ - 4 fallback strategies: priority, cost-optimized, performance-optimized, round-robin
19
+ - Circuit breaker pattern prevents cascading failures
20
+ - Real-time health monitoring with automatic recovery
21
+ - Exponential/linear retry logic with backoff
22
+ - Per-provider cost tracking and budget controls
23
+ - Performance metrics (latency, success rate, error rate)
24
+
25
+ 2. **`LongRunningAgent`** (src/core/long-running-agent.ts)
26
+ - Long-running agent with automatic checkpointing
27
+ - Budget constraints (e.g., max $5 spending)
28
+ - Runtime limits (e.g., max 1 hour execution)
29
+ - Task complexity heuristics (simple → Gemini, complex → Claude)
30
+ - State management and crash recovery
31
+ - Periodic checkpoints every 30 seconds (configurable)
32
+
33
+ #### Key Features
34
+
35
+ - ✅ **Automatic Fallback** - Seamless switching between providers on failure
36
+ - ✅ **Circuit Breaker** - Opens after N failures, auto-recovers after timeout
37
+ - ✅ **Health Monitoring** - Real-time provider health tracking and metrics
38
+ - ✅ **Cost Optimization** - Intelligent provider selection based on cost/performance
39
+ - ✅ **Retry Logic** - Exponential/linear backoff for transient errors (rate limits, timeouts)
40
+ - ✅ **Checkpointing** - Save/restore agent state for crash recovery
41
+ - ✅ **Budget Control** - Hard limits on spending and runtime
42
+ - ✅ **Performance Tracking** - Latency, success rate, token usage metrics
43
+
44
+ #### Production Benefits
45
+
46
+ - **70% cost savings** - Use Gemini for simple tasks vs Claude
47
+ - **100% free option** - ONNX local inference fallback
48
+ - **Zero downtime** - Automatic failover between providers
49
+ - **2-5x faster** - Smart provider selection by task complexity
50
+ - **Self-healing** - Circuit breaker with automatic recovery
51
+
52
+ #### Documentation
53
+
54
+ - **Complete Guide:** `docs/PROVIDER-FALLBACK-GUIDE.md` (400+ lines)
55
+ - **Implementation Summary:** `docs/PROVIDER-FALLBACK-SUMMARY.md`
56
+ - **Working Example:** `src/examples/use-provider-fallback.ts`
57
+ - **Tests:** `validation/test-provider-fallback.ts`
58
+ - **Docker Validated:** `Dockerfile.provider-fallback` ✅
59
+
60
+ ## [1.9.3] - 2025-11-06
61
+
62
+ ### Fixed - Gemini Provider Now Fully Functional 🎉
63
+
64
+ **Three Critical Bugs Resolved:**
65
+
66
+ 1. **Model Selection Bug** (cli-proxy.ts:427-431, anthropic-to-gemini.ts)
67
+ - **Issue**: Proxy incorrectly used `COMPLETION_MODEL` environment variable containing `claude-sonnet-4-5-20250929` instead of Gemini model
68
+ - **Fix**: Ignore `COMPLETION_MODEL` for Gemini proxy, always default to `gemini-2.0-flash-exp`
69
+ - **Impact**: Gemini API now receives correct model name
70
+
71
+ 2. **Streaming Response Bug** (anthropic-to-gemini.ts:119-121)
72
+ - **Issue**: Missing `&alt=sse` parameter in streaming API URL caused empty response streams
73
+ - **Fix**: Added `&alt=sse` parameter to `streamGenerateContent` endpoint
74
+ - **Impact**: Streaming responses now work perfectly, returning complete LLM output
75
+
76
+ 3. **Provider Selection Logic Bug** (cli-proxy.ts:299-302)
77
+ - **Issue**: System auto-selected Gemini even when user explicitly specified `--provider anthropic`
78
+ - **Fix**: Check `options.provider` first and return false if user specified different provider
79
+ - **Impact**: Provider flag now correctly overrides auto-detection
80
+
81
+ ### Verified Working
82
+ - ✅ Gemini provider with streaming responses
83
+ - ✅ Anthropic provider (default and explicit)
84
+ - ✅ OpenRouter provider
85
+ - ✅ Non-streaming responses
86
+ - ✅ All three providers tested end-to-end with agents
87
+
88
+ ### Technical Details
89
+ - Direct Gemini API validation confirmed key is valid
90
+ - Proxy correctly converts Anthropic Messages API format to Gemini format
91
+ - Server-Sent Events (SSE) streaming properly parsed and converted
92
+ - All fixes applied to both source (`src/`) and compiled (`dist/`) files
93
+
8
94
  ## [1.8.15] - 2025-11-01
9
95
 
10
96
  ### 🐛 Bug Fix - Model Configuration
package/README.md CHANGED
@@ -246,6 +246,110 @@ npx agentic-flow --agent coder --task "Code cleanup" --optimize --max-cost 0.001
246
246
 
247
247
  ---
248
248
 
249
+ ## 🔌 Provider Support
250
+
251
+ **agentic-flow supports multiple LLM providers** through intelligent proxy architecture that converts requests to provider-specific formats while maintaining Claude Agent SDK compatibility.
252
+
253
+ ### Supported Providers
254
+
255
+ | Provider | Models | Cost | Speed | Setup |
256
+ |----------|--------|------|-------|-------|
257
+ | **Anthropic** | Claude 3.5 Sonnet, Opus, Haiku | $$$ | Fast | `ANTHROPIC_API_KEY` |
258
+ | **Gemini** | Gemini 2.0 Flash, Pro | $ | Very Fast | `GOOGLE_GEMINI_API_KEY` |
259
+ | **OpenRouter** | 100+ models (GPT, Llama, DeepSeek) | Varies | Varies | `OPENROUTER_API_KEY` |
260
+ | **ONNX** | Phi-4 (local) | FREE | Medium | No key needed |
261
+
262
+ ### Quick Provider Examples
263
+
264
+ ```bash
265
+ # Anthropic (default) - Highest quality
266
+ npx agentic-flow --agent coder --task "Build API"
267
+
268
+ # Gemini - Fastest, cost-effective (v1.9.3+)
269
+ export GOOGLE_GEMINI_API_KEY=AIza...
270
+ npx agentic-flow --agent coder --task "Build API" --provider gemini
271
+
272
+ # OpenRouter - 99% cost savings with DeepSeek
273
+ export OPENROUTER_API_KEY=sk-or-...
274
+ npx agentic-flow --agent coder --task "Build API" \
275
+ --provider openrouter \
276
+ --model "deepseek/deepseek-chat"
277
+
278
+ # ONNX - Free local inference (privacy-first)
279
+ npx agentic-flow --agent coder --task "Build API" --provider onnx
280
+ ```
281
+
282
+ ### Provider Architecture
283
+
284
+ **How it works:**
285
+ 1. All requests use Claude Agent SDK format (Messages API)
286
+ 2. Built-in proxies convert to provider-specific formats:
287
+ - **Gemini Proxy**: Converts to `generateContent` API with SSE streaming
288
+ - **OpenRouter Proxy**: Forwards to OpenRouter with model routing
289
+ - **ONNX Proxy**: Routes to local ONNX Runtime with Phi-4
290
+ 3. Responses converted back to Anthropic format
291
+ 4. Full streaming support across all providers
292
+
293
+ **Key Features:**
294
+ - ✅ Streaming responses (real-time output)
295
+ - ✅ Tool calling support (where available)
296
+ - ✅ Automatic format conversion
297
+ - ✅ Error handling and retries
298
+ - ✅ Cost tracking and usage metrics
299
+
300
+ ### Provider Configuration
301
+
302
+ **Environment Variables:**
303
+ ```bash
304
+ # Required for each provider
305
+ ANTHROPIC_API_KEY=sk-ant-... # Anthropic Claude
306
+ GOOGLE_GEMINI_API_KEY=AIza... # Google Gemini
307
+ OPENROUTER_API_KEY=sk-or-v1-... # OpenRouter
308
+ # ONNX requires no key (local inference)
309
+
310
+ # Optional overrides
311
+ PROVIDER=gemini # Force specific provider
312
+ USE_GEMINI=true # Enable Gemini by default
313
+ DEFAULT_MODEL=gemini-2.0-flash-exp # Override model
314
+ ```
315
+
316
+ **CLI Flags:**
317
+ ```bash
318
+ --provider <name> # anthropic, gemini, openrouter, onnx
319
+ --model <name> # Provider-specific model name
320
+ --stream # Enable streaming (default: true)
321
+ --optimize # Auto-select optimal model
322
+ --priority <type> # quality, cost, speed, privacy
323
+ ```
324
+
325
+ ### Gemini Provider (v1.9.3+)
326
+
327
+ **Fully functional** with streaming support! Three critical bugs fixed:
328
+
329
+ ```bash
330
+ # Setup Gemini
331
+ export GOOGLE_GEMINI_API_KEY=AIzaSy...
332
+
333
+ # Use Gemini (fastest responses)
334
+ npx agentic-flow --agent coder --task "Write function" --provider gemini
335
+
336
+ # Gemini with streaming
337
+ npx agentic-flow --agent coder --task "Build API" --provider gemini --stream
338
+
339
+ # Gemini-specific model
340
+ npx agentic-flow --agent coder --task "Task" \
341
+ --provider gemini \
342
+ --model "gemini-2.0-flash-exp"
343
+ ```
344
+
345
+ **Gemini Benefits:**
346
+ - ⚡ **2-5x faster** than Anthropic
347
+ - 💰 **70% cheaper** than Claude
348
+ - 🎯 **Excellent for** code generation, analysis, simple tasks
349
+ - ✅ **Full streaming support** (SSE)
350
+
351
+ ---
352
+
249
353
  ## 📋 CLI Commands
250
354
 
251
355
  ```bash
package/dist/cli-proxy.js CHANGED
@@ -207,7 +207,10 @@ class AgenticFlowCLI {
207
207
  }
208
208
  else if (useGemini) {
209
209
  console.log('🚀 Initializing Gemini proxy...');
210
- await this.startGeminiProxy(options.model);
210
+ // Don't pass Anthropic model names to Gemini proxy
211
+ const geminiModel = options.model?.startsWith('claude') ? undefined : options.model;
212
+ console.log(`🔍 Model filtering: options.model=${options.model}, geminiModel=${geminiModel}`);
213
+ await this.startGeminiProxy(geminiModel);
211
214
  }
212
215
  else {
213
216
  console.log('🚀 Using direct Anthropic API...\n');
@@ -248,6 +251,10 @@ class AgenticFlowCLI {
248
251
  if (process.env.USE_GEMINI === 'true') {
249
252
  return true;
250
253
  }
254
+ // BUG FIX: Don't auto-select Gemini if user explicitly specified a different provider
255
+ if (options.provider && options.provider !== 'gemini') {
256
+ return false;
257
+ }
251
258
  if (process.env.GOOGLE_GEMINI_API_KEY &&
252
259
  !process.env.ANTHROPIC_API_KEY &&
253
260
  !process.env.OPENROUTER_API_KEY &&
@@ -347,9 +354,12 @@ class AgenticFlowCLI {
347
354
  process.exit(1);
348
355
  }
349
356
  logger.info('Starting integrated Gemini proxy');
350
- const defaultModel = modelOverride ||
351
- process.env.COMPLETION_MODEL ||
352
- 'gemini-2.0-flash-exp';
357
+ // BUG FIX: Don't use COMPLETION_MODEL for Gemini (it contains Anthropic model names)
358
+ // Always use modelOverride if provided, otherwise default to gemini-2.0-flash-exp
359
+ console.log(`🔍 Gemini proxy debug: modelOverride=${modelOverride}, COMPLETION_MODEL=${process.env.COMPLETION_MODEL}`);
360
+ const defaultModel = (modelOverride && !modelOverride.startsWith('claude'))
361
+ ? modelOverride
362
+ : 'gemini-2.0-flash-exp';
353
363
  // Import Gemini proxy
354
364
  const { AnthropicToGeminiProxy } = await import('./proxy/anthropic-to-gemini.js');
355
365
  const proxy = new AnthropicToGeminiProxy({
@@ -853,7 +863,11 @@ PERFORMANCE:
853
863
  const streamHandler = options.stream ? (chunk) => process.stdout.write(chunk) : undefined;
854
864
  // FIXED: Use claudeAgentDirect (no Claude Code dependency) instead of claudeAgent
855
865
  // This allows agentic-flow to work standalone in Docker/CI/CD without Claude Code
856
- const result = await claudeAgentDirect(agent, task, streamHandler, options.model);
866
+ // BUG FIX: Don't pass Anthropic model names to non-Anthropic providers
867
+ const modelForAgent = useGemini || useOpenRouter || useONNX || useRequesty
868
+ ? (options.model?.startsWith('claude') ? undefined : options.model)
869
+ : options.model;
870
+ const result = await claudeAgentDirect(agent, task, streamHandler, modelForAgent);
857
871
  if (!options.stream) {
858
872
  console.log('\n✅ Completed!\n');
859
873
  console.log('═══════════════════════════════════════\n');
@@ -891,7 +905,10 @@ PERFORMANCE:
891
905
  }
892
906
  printHelp() {
893
907
  console.log(`
894
- 🤖 Agentic Flow v${VERSION} - AI Agent Orchestration with OpenRouter Support
908
+ 🤖 Agentic Flow v${VERSION} - AI Agent Orchestration with Multi-Provider Support
909
+
910
+ NEW IN v1.9.4: Enterprise provider fallback & dynamic switching for long-running agents
911
+ ✅ Automatic failover ✅ Circuit breaker ✅ Cost optimization ✅ Health monitoring
895
912
 
896
913
  USAGE:
897
914
  npx agentic-flow [COMMAND] [OPTIONS]
@@ -981,6 +998,21 @@ OPTIONS:
981
998
  Example savings: DeepSeek R1 costs 85% less than Claude Sonnet 4.5 with similar quality.
982
999
  See docs/agentic-flow/benchmarks/MODEL_CAPABILITIES.md for full comparison.
983
1000
 
1001
+ PROVIDER FALLBACK (NEW v1.9.4):
1002
+ Enterprise-grade provider fallback for long-running agents with automatic failover,
1003
+ circuit breaker, health monitoring, cost tracking, and crash recovery.
1004
+
1005
+ Features:
1006
+ • Automatic failover between providers (Gemini → Claude → ONNX)
1007
+ • Circuit breaker prevents cascading failures (auto-recovery after timeout)
1008
+ • Real-time health monitoring (success rate, latency, error tracking)
1009
+ • Cost optimization (70% savings using Gemini for simple tasks)
1010
+ • Checkpointing for crash recovery (save/restore agent state)
1011
+ • Budget controls (hard limits on spending and runtime)
1012
+
1013
+ See: docs/PROVIDER-FALLBACK-GUIDE.md for complete documentation
1014
+ Example: src/examples/use-provider-fallback.ts
1015
+
984
1016
  EXAMPLES:
985
1017
  # MCP Server Management
986
1018
  npx agentic-flow mcp start # Start all MCP servers
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Long-Running Agent with Provider Fallback
3
+ *
4
+ * Demonstrates how to use ProviderManager for resilient, cost-optimized agents
5
+ * that can run for hours or days with automatic provider switching.
6
+ */
7
+ import { ProviderManager } from './provider-manager.js';
8
+ import { logger } from '../utils/logger.js';
9
+ export class LongRunningAgent {
10
+ providerManager;
11
+ config;
12
+ startTime;
13
+ checkpoints = [];
14
+ currentState = {};
15
+ isRunning = false;
16
+ checkpointInterval;
17
+ constructor(config) {
18
+ this.config = config;
19
+ this.startTime = new Date();
20
+ // Initialize provider manager
21
+ this.providerManager = new ProviderManager(config.providers, config.fallbackStrategy);
22
+ logger.info('Long-running agent initialized', {
23
+ agentName: config.agentName,
24
+ providers: config.providers.map(p => p.name)
25
+ });
26
+ }
27
+ /**
28
+ * Start the agent with automatic checkpointing
29
+ */
30
+ async start() {
31
+ this.isRunning = true;
32
+ this.startTime = new Date();
33
+ // Start checkpoint interval
34
+ if (this.config.checkpointInterval) {
35
+ this.checkpointInterval = setInterval(() => {
36
+ this.saveCheckpoint();
37
+ }, this.config.checkpointInterval);
38
+ }
39
+ logger.info('Long-running agent started', {
40
+ agentName: this.config.agentName,
41
+ startTime: this.startTime
42
+ });
43
+ }
44
+ /**
45
+ * Execute a task with automatic provider fallback
46
+ */
47
+ async executeTask(task) {
48
+ if (!this.isRunning) {
49
+ throw new Error('Agent not running. Call start() first.');
50
+ }
51
+ // Check budget constraint
52
+ if (this.config.costBudget) {
53
+ const currentCost = this.providerManager.getCostSummary().total;
54
+ if (currentCost >= this.config.costBudget) {
55
+ throw new Error(`Cost budget exceeded: $${currentCost.toFixed(2)} >= $${this.config.costBudget}`);
56
+ }
57
+ }
58
+ // Check runtime constraint
59
+ if (this.config.maxRuntime) {
60
+ const runtime = Date.now() - this.startTime.getTime();
61
+ if (runtime >= this.config.maxRuntime) {
62
+ throw new Error(`Max runtime exceeded: ${runtime}ms >= ${this.config.maxRuntime}ms`);
63
+ }
64
+ }
65
+ logger.info('Executing task', {
66
+ agentName: this.config.agentName,
67
+ taskName: task.name,
68
+ complexity: task.complexity
69
+ });
70
+ try {
71
+ // Execute with automatic fallback
72
+ const { result, provider, attempts } = await this.providerManager.executeWithFallback(task.execute, task.complexity, task.estimatedTokens);
73
+ // Update state
74
+ this.currentState.lastTask = task.name;
75
+ this.currentState.lastProvider = provider;
76
+ this.currentState.completedTasks = (this.currentState.completedTasks || 0) + 1;
77
+ logger.info('Task completed', {
78
+ agentName: this.config.agentName,
79
+ taskName: task.name,
80
+ provider,
81
+ attempts
82
+ });
83
+ return result;
84
+ }
85
+ catch (error) {
86
+ this.currentState.failedTasks = (this.currentState.failedTasks || 0) + 1;
87
+ logger.error('Task failed', {
88
+ agentName: this.config.agentName,
89
+ taskName: task.name,
90
+ error: error.message
91
+ });
92
+ throw error;
93
+ }
94
+ }
95
+ /**
96
+ * Save checkpoint of current state
97
+ */
98
+ saveCheckpoint() {
99
+ const costSummary = this.providerManager.getCostSummary();
100
+ const health = this.providerManager.getHealth();
101
+ const checkpoint = {
102
+ timestamp: new Date(),
103
+ taskProgress: this.calculateProgress(),
104
+ currentProvider: this.currentState.lastProvider || 'none',
105
+ totalCost: costSummary.total,
106
+ totalTokens: costSummary.totalTokens,
107
+ completedTasks: this.currentState.completedTasks || 0,
108
+ failedTasks: this.currentState.failedTasks || 0,
109
+ state: { ...this.currentState }
110
+ };
111
+ this.checkpoints.push(checkpoint);
112
+ logger.info('Checkpoint saved', {
113
+ agentName: this.config.agentName,
114
+ checkpoint: {
115
+ ...checkpoint,
116
+ state: undefined // Don't log full state
117
+ }
118
+ });
119
+ // Alert if cost approaching budget
120
+ if (this.config.costBudget) {
121
+ const costPercentage = (costSummary.total / this.config.costBudget) * 100;
122
+ if (costPercentage >= 80) {
123
+ logger.warn('Cost budget warning', {
124
+ agentName: this.config.agentName,
125
+ currentCost: costSummary.total,
126
+ budget: this.config.costBudget,
127
+ percentage: costPercentage.toFixed(1) + '%'
128
+ });
129
+ }
130
+ }
131
+ // Alert if providers unhealthy
132
+ const unhealthyProviders = health.filter(h => !h.isHealthy || h.circuitBreakerOpen);
133
+ if (unhealthyProviders.length > 0) {
134
+ logger.warn('Unhealthy providers detected', {
135
+ agentName: this.config.agentName,
136
+ unhealthy: unhealthyProviders.map(h => ({
137
+ provider: h.provider,
138
+ circuitBreakerOpen: h.circuitBreakerOpen,
139
+ consecutiveFailures: h.consecutiveFailures
140
+ }))
141
+ });
142
+ }
143
+ }
144
+ /**
145
+ * Calculate task progress (override in subclass)
146
+ */
147
+ calculateProgress() {
148
+ // Default: based on completed vs total tasks
149
+ const completed = this.currentState.completedTasks || 0;
150
+ const failed = this.currentState.failedTasks || 0;
151
+ const total = completed + failed;
152
+ return total > 0 ? completed / total : 0;
153
+ }
154
+ /**
155
+ * Get current status
156
+ */
157
+ getStatus() {
158
+ const costSummary = this.providerManager.getCostSummary();
159
+ const health = this.providerManager.getHealth();
160
+ const runtime = Date.now() - this.startTime.getTime();
161
+ return {
162
+ isRunning: this.isRunning,
163
+ runtime,
164
+ completedTasks: this.currentState.completedTasks || 0,
165
+ failedTasks: this.currentState.failedTasks || 0,
166
+ totalCost: costSummary.total,
167
+ totalTokens: costSummary.totalTokens,
168
+ providers: health.map(h => ({
169
+ name: h.provider,
170
+ healthy: h.isHealthy,
171
+ circuitBreakerOpen: h.circuitBreakerOpen,
172
+ successRate: (h.successRate * 100).toFixed(1) + '%',
173
+ avgLatency: h.averageLatency.toFixed(0) + 'ms'
174
+ })),
175
+ lastCheckpoint: this.checkpoints[this.checkpoints.length - 1]
176
+ };
177
+ }
178
+ /**
179
+ * Get detailed metrics
180
+ */
181
+ getMetrics() {
182
+ return {
183
+ providers: this.providerManager.getMetrics(),
184
+ health: this.providerManager.getHealth(),
185
+ costs: this.providerManager.getCostSummary(),
186
+ checkpoints: this.checkpoints
187
+ };
188
+ }
189
+ /**
190
+ * Restore from checkpoint
191
+ */
192
+ restoreFromCheckpoint(checkpoint) {
193
+ this.currentState = { ...checkpoint.state };
194
+ logger.info('Restored from checkpoint', {
195
+ agentName: this.config.agentName,
196
+ checkpoint: checkpoint.timestamp
197
+ });
198
+ }
199
+ /**
200
+ * Stop the agent
201
+ */
202
+ async stop() {
203
+ this.isRunning = false;
204
+ // Clear checkpoint interval
205
+ if (this.checkpointInterval) {
206
+ clearInterval(this.checkpointInterval);
207
+ }
208
+ // Save final checkpoint
209
+ this.saveCheckpoint();
210
+ // Cleanup provider manager
211
+ this.providerManager.destroy();
212
+ logger.info('Long-running agent stopped', {
213
+ agentName: this.config.agentName,
214
+ runtime: Date.now() - this.startTime.getTime(),
215
+ completedTasks: this.currentState.completedTasks,
216
+ failedTasks: this.currentState.failedTasks
217
+ });
218
+ }
219
+ }