codex-router 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Intellirim
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: codex-router
3
+ Version: 0.1.0
4
+ Summary: Intelligent routing and orchestration for multi-model AI coding agents
5
+ Author-email: Intellirim <hello@intellirim.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Intellirim/codex-router
8
+ Project-URL: Issues, https://github.com/Intellirim/codex-router/issues
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: click>=8.1.0
21
+ Requires-Dist: anthropic>=0.18.0
22
+ Requires-Dist: openai>=1.12.0
23
+ Requires-Dist: google-generativeai>=0.3.0
24
+ Requires-Dist: pydantic>=2.5.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
29
+ Requires-Dist: black>=23.0.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # codex-router
34
+
35
+ ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
36
+
37
+ **Intelligent routing and orchestration for multi-model AI coding agents**
38
+
39
+ `codex-router` is a lightweight CLI tool that automatically routes coding tasks to the best available AI model (Claude, GPT, Gemini, or local) based on task complexity, cost, and availability. Manage parallel agent sessions, track token usage and costs across providers, and get unified output streaming. Makes it trivial to leverage multiple AI models without manual context switching.
40
+
41
+ ## Features
42
+
43
+ - **Smart routing**: Analyzes task complexity and routes to optimal model (fast models for simple tasks, frontier models for complex ones)
44
+ - **Parallel orchestration**: Run multiple AI agents on different subtasks simultaneously with unified output
45
+ - **Cost tracking**: Real-time token usage and cost monitoring across all providers (Claude, OpenAI, Gemini)
46
+ - **Auto-fallback**: Automatically switches to alternative model if primary hits rate limits or errors
47
+ - **Unified config**: Single configuration file for all API keys and preferences
48
+ - **Session management**: Save and resume multi-agent sessions with full context
49
+ - **ASCII-only output**: Cross-platform terminal compatibility (Windows, macOS, Linux)
50
+
51
+ ## Installation
52
+
53
+ Install via pip:
54
+
55
+ ```bash
56
+ pip install codex-router
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ Configure your API keys:
62
+
63
+ ```bash
64
+ codex-router config --set anthropic_api_key YOUR_CLAUDE_KEY
65
+ codex-router config --set openai_api_key YOUR_OPENAI_KEY
66
+ codex-router config --set google_api_key YOUR_GEMINI_KEY
67
+ ```
68
+
69
+ Set default preferences:
70
+
71
+ ```bash
72
+ codex-router config --set-default-model claude
73
+ ```
74
+
75
+ ## Usage Examples
76
+
77
+ Run a single coding task with intelligent model selection:
78
+
79
+ ```bash
80
+ codex-router task "refactor auth module"
81
+ ```
82
+
83
+ Output:
84
+ ```
85
+ [Router] Analyzing task complexity...
86
+ [Router] Task complexity: HIGH -> Selected model: claude-opus-4
87
+ [Agent-1] Reading auth module...
88
+ [Agent-1] Identified 3 refactoring opportunities
89
+ [Agent-1] Applying changes...
90
+ [Agent-1] DONE - 2,450 tokens used ($0.073)
91
+ ```
92
+
93
+ Run parallel agents on multiple subtasks:
94
+
95
+ ```bash
96
+ codex-router task "add unit tests" --parallel 2
97
+ ```
98
+
99
+ Output:
100
+ ```
101
+ [Router] Splitting task into 2 parallel agents
102
+ [Agent-1] Testing user authentication flow...
103
+ [Agent-2] Testing database connections...
104
+ [Agent-1] Created 5 test cases
105
+ [Agent-2] Created 3 test cases
106
+ [Router] DONE - Total: 3,120 tokens ($0.094)
107
+ ```
108
+
109
+ Constrain budget for cost control:
110
+
111
+ ```bash
112
+ codex-router task "add unit tests" --model claude --budget 0.50
113
+ ```
114
+
115
+ Check usage statistics:
116
+
117
+ ```bash
118
+ codex-router status --show-costs
119
+ ```
120
+
121
+ Output:
122
+ ```
123
+ Token Usage Summary (Last 7 Days)
124
+ ---------------------------------
125
+ Provider | Tokens | Cost
126
+ ---------------------------------
127
+ Claude | 45,230 | $1.35
128
+ OpenAI | 12,500 | $0.25
129
+ Gemini | 8,900 | $0.00
130
+ ---------------------------------
131
+ Total | $1.60
132
+ ```
133
+
134
+ ## Configuration
135
+
136
+ Configuration is stored in `~/.codex-router/config.yaml`. You can edit it manually or use the CLI:
137
+
138
+ ```bash
139
+ # Set API keys
140
+ codex-router config --set anthropic_api_key YOUR_KEY
141
+ codex-router config --set openai_api_key YOUR_KEY
142
+ codex-router config --set google_api_key YOUR_KEY
143
+
144
+ # Set default model
145
+ codex-router config --set-default-model gpt-4
146
+
147
+ # Set budget limits
148
+ codex-router config --set daily_budget 5.00
149
+ ```
150
+
151
+ ## How It Works
152
+
153
+ 1. **Task Analysis**: The router analyzes your task description for complexity, required context, and estimated token usage
154
+ 2. **Model Selection**: Based on complexity and your preferences, selects the optimal model (e.g., GPT-3.5 for simple tasks, Claude Opus for complex refactoring)
155
+ 3. **Execution**: Sends the task to the selected provider's API with proper context and streaming
156
+ 4. **Cost Tracking**: Records token usage and costs in a local database for monitoring
157
+ 5. **Auto-Fallback**: If the primary model fails (rate limit, timeout), automatically retries with an alternative model
158
+
159
+ ## Development
160
+
161
+ Clone the repository:
162
+
163
+ ```bash
164
+ git clone https://github.com/Intellirim/codex-router.git
165
+ cd codex-router
166
+ ```
167
+
168
+ Install in development mode:
169
+
170
+ ```bash
171
+ pip install -e .
172
+ ```
173
+
174
+ Run tests:
175
+
176
+ ```bash
177
+ pytest tests/
178
+ ```
179
+
180
+ ## License
181
+
182
+ MIT License - Copyright (c) 2026 Intellirim
183
+
184
+ ## Contributing
185
+
186
+ Contributions welcome! Please open an issue or submit a pull request.
@@ -0,0 +1,154 @@
1
+ # codex-router
2
+
3
+ ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
4
+
5
+ **Intelligent routing and orchestration for multi-model AI coding agents**
6
+
7
+ `codex-router` is a lightweight CLI tool that automatically routes coding tasks to the best available AI model (Claude, GPT, Gemini, or local) based on task complexity, cost, and availability. Manage parallel agent sessions, track token usage and costs across providers, and get unified output streaming. Makes it trivial to leverage multiple AI models without manual context switching.
8
+
9
+ ## Features
10
+
11
+ - **Smart routing**: Analyzes task complexity and routes to optimal model (fast models for simple tasks, frontier models for complex ones)
12
+ - **Parallel orchestration**: Run multiple AI agents on different subtasks simultaneously with unified output
13
+ - **Cost tracking**: Real-time token usage and cost monitoring across all providers (Claude, OpenAI, Gemini)
14
+ - **Auto-fallback**: Automatically switches to alternative model if primary hits rate limits or errors
15
+ - **Unified config**: Single configuration file for all API keys and preferences
16
+ - **Session management**: Save and resume multi-agent sessions with full context
17
+ - **ASCII-only output**: Cross-platform terminal compatibility (Windows, macOS, Linux)
18
+
19
+ ## Installation
20
+
21
+ Install via pip:
22
+
23
+ ```bash
24
+ pip install codex-router
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ Configure your API keys:
30
+
31
+ ```bash
32
+ codex-router config --set anthropic_api_key YOUR_CLAUDE_KEY
33
+ codex-router config --set openai_api_key YOUR_OPENAI_KEY
34
+ codex-router config --set google_api_key YOUR_GEMINI_KEY
35
+ ```
36
+
37
+ Set default preferences:
38
+
39
+ ```bash
40
+ codex-router config --set-default-model claude
41
+ ```
42
+
43
+ ## Usage Examples
44
+
45
+ Run a single coding task with intelligent model selection:
46
+
47
+ ```bash
48
+ codex-router task "refactor auth module"
49
+ ```
50
+
51
+ Output:
52
+ ```
53
+ [Router] Analyzing task complexity...
54
+ [Router] Task complexity: HIGH -> Selected model: claude-opus-4
55
+ [Agent-1] Reading auth module...
56
+ [Agent-1] Identified 3 refactoring opportunities
57
+ [Agent-1] Applying changes...
58
+ [Agent-1] DONE - 2,450 tokens used ($0.073)
59
+ ```
60
+
61
+ Run parallel agents on multiple subtasks:
62
+
63
+ ```bash
64
+ codex-router task "add unit tests" --parallel 2
65
+ ```
66
+
67
+ Output:
68
+ ```
69
+ [Router] Splitting task into 2 parallel agents
70
+ [Agent-1] Testing user authentication flow...
71
+ [Agent-2] Testing database connections...
72
+ [Agent-1] Created 5 test cases
73
+ [Agent-2] Created 3 test cases
74
+ [Router] DONE - Total: 3,120 tokens ($0.094)
75
+ ```
76
+
77
+ Constrain budget for cost control:
78
+
79
+ ```bash
80
+ codex-router task "add unit tests" --model claude --budget 0.50
81
+ ```
82
+
83
+ Check usage statistics:
84
+
85
+ ```bash
86
+ codex-router status --show-costs
87
+ ```
88
+
89
+ Output:
90
+ ```
91
+ Token Usage Summary (Last 7 Days)
92
+ ---------------------------------
93
+ Provider | Tokens | Cost
94
+ ---------------------------------
95
+ Claude | 45,230 | $1.35
96
+ OpenAI | 12,500 | $0.25
97
+ Gemini | 8,900 | $0.00
98
+ ---------------------------------
99
+ Total | $1.60
100
+ ```
101
+
102
+ ## Configuration
103
+
104
+ Configuration is stored in `~/.codex-router/config.yaml`. You can edit it manually or use the CLI:
105
+
106
+ ```bash
107
+ # Set API keys
108
+ codex-router config --set anthropic_api_key YOUR_KEY
109
+ codex-router config --set openai_api_key YOUR_KEY
110
+ codex-router config --set google_api_key YOUR_KEY
111
+
112
+ # Set default model
113
+ codex-router config --set-default-model gpt-4
114
+
115
+ # Set budget limits
116
+ codex-router config --set daily_budget 5.00
117
+ ```
118
+
119
+ ## How It Works
120
+
121
+ 1. **Task Analysis**: The router analyzes your task description for complexity, required context, and estimated token usage
122
+ 2. **Model Selection**: Based on complexity and your preferences, selects the optimal model (e.g., GPT-3.5 for simple tasks, Claude Opus for complex refactoring)
123
+ 3. **Execution**: Sends the task to the selected provider's API with proper context and streaming
124
+ 4. **Cost Tracking**: Records token usage and costs in a local database for monitoring
125
+ 5. **Auto-Fallback**: If the primary model fails (rate limit, timeout), automatically retries with an alternative model
126
+
127
+ ## Development
128
+
129
+ Clone the repository:
130
+
131
+ ```bash
132
+ git clone https://github.com/Intellirim/codex-router.git
133
+ cd codex-router
134
+ ```
135
+
136
+ Install in development mode:
137
+
138
+ ```bash
139
+ pip install -e .
140
+ ```
141
+
142
+ Run tests:
143
+
144
+ ```bash
145
+ pytest tests/
146
+ ```
147
+
148
+ ## License
149
+
150
+ MIT License - Copyright (c) 2026 Intellirim
151
+
152
+ ## Contributing
153
+
154
+ Contributions welcome! Please open an issue or submit a pull request.
@@ -0,0 +1,11 @@
1
+ """codex-router: Intelligent routing and orchestration for multi-model AI coding agents."""
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "Intellirim"
5
+ __license__ = "MIT"
6
+
7
+ from codex_router.router import TaskRouter
8
+ from codex_router.config import Config
9
+ from codex_router.tracker import CostTracker
10
+
11
+ __all__ = ["TaskRouter", "Config", "CostTracker", "__version__"]
@@ -0,0 +1,134 @@
1
+ """CLI entry point for codex-router."""
2
+
3
+ import click
4
+ from pathlib import Path
5
+ from codex_router import __version__
6
+ from codex_router.config import Config
7
+ from codex_router.router import TaskRouter
8
+ from codex_router.orchestrator import Orchestrator
9
+ from codex_router.tracker import CostTracker
10
+ from codex_router.display import Display
11
+
12
+
13
+ @click.group()
14
+ @click.version_option(version=__version__)
15
+ def main():
16
+ """Intelligent routing and orchestration for multi-model AI coding agents."""
17
+ pass
18
+
19
+
20
+ @main.command()
21
+ @click.argument("description")
22
+ @click.option("--model", "-m", help="Force specific model (claude, gpt-4, gemini)")
23
+ @click.option("--parallel", "-p", type=int, default=1, help="Number of parallel agents")
24
+ @click.option("--budget", "-b", type=float, help="Maximum budget in USD")
25
+ def task(description: str, model: str, parallel: int, budget: float):
26
+ """Execute a coding task with intelligent routing."""
27
+ try:
28
+ config = Config.load()
29
+ except FileNotFoundError:
30
+ click.echo("Error: Config not found. Run 'codex-router config --init' first.", err=True)
31
+ raise SystemExit(1)
32
+
33
+ display = Display()
34
+ tracker = CostTracker()
35
+
36
+ if budget and budget <= 0:
37
+ click.echo("Error: Budget must be positive", err=True)
38
+ raise SystemExit(1)
39
+
40
+ router = TaskRouter(config)
41
+
42
+ if parallel > 1:
43
+ display.info(f"Splitting task into {parallel} parallel agents")
44
+ orchestrator = Orchestrator(config, tracker)
45
+ try:
46
+ result = orchestrator.run_parallel(description, parallel, model, budget)
47
+ display.success(f"DONE - Total: {result['total_tokens']} tokens (${result['total_cost']:.3f})")
48
+ except Exception as e:
49
+ display.error(f"Orchestration failed: {str(e)}")
50
+ raise SystemExit(2)
51
+ else:
52
+ display.info("Analyzing task complexity...")
53
+ try:
54
+ selected_model = router.select_model(description, force_model=model)
55
+ display.info(f"Selected model: {selected_model}")
56
+
57
+ result = router.execute_task(description, selected_model, budget)
58
+ tracker.record_usage(selected_model, result["tokens"], result["cost"])
59
+
60
+ display.show_result(result["output"])
61
+ display.success(f"DONE - {result['tokens']} tokens (${result['cost']:.3f})")
62
+ except ValueError as e:
63
+ display.error(str(e))
64
+ raise SystemExit(1)
65
+ except Exception as e:
66
+ display.error(f"Task execution failed: {str(e)}")
67
+ raise SystemExit(2)
68
+
69
+
70
+ @main.command()
71
+ @click.option("--show-costs", is_flag=True, help="Show cost breakdown by provider")
72
+ @click.option("--days", "-d", type=int, default=7, help="Number of days to show")
73
+ def status(show_costs: bool, days: int):
74
+ """Show usage statistics and costs."""
75
+ tracker = CostTracker()
76
+ display = Display()
77
+
78
+ try:
79
+ stats = tracker.get_stats(days)
80
+ except Exception as e:
81
+ click.echo(f"Error: Failed to load statistics: {str(e)}", err=True)
82
+ raise SystemExit(2)
83
+
84
+ if show_costs:
85
+ display.show_cost_table(stats, days)
86
+ else:
87
+ click.echo(f"Total requests (last {days} days): {stats['total_requests']}")
88
+ click.echo(f"Total tokens: {stats['total_tokens']}")
89
+ click.echo(f"Total cost: ${stats['total_cost']:.2f}")
90
+
91
+
92
+ @main.command()
93
+ @click.option("--set", "set_value", nargs=2, metavar="KEY VALUE", help="Set config value")
94
+ @click.option("--set-default-model", metavar="MODEL", help="Set default model")
95
+ @click.option("--init", is_flag=True, help="Initialize config with defaults")
96
+ def config(set_value: tuple, set_default_model: str, init: bool):
97
+ """Manage configuration."""
98
+ if init:
99
+ try:
100
+ Config.init_default()
101
+ click.echo("Config initialized at ~/.codex-router/config.yaml")
102
+ except Exception as e:
103
+ click.echo(f"Error: Failed to initialize config: {str(e)}", err=True)
104
+ raise SystemExit(2)
105
+ return
106
+
107
+ try:
108
+ cfg = Config.load()
109
+ except FileNotFoundError:
110
+ click.echo("Error: Config not found. Run 'codex-router config --init' first.", err=True)
111
+ raise SystemExit(1)
112
+
113
+ if set_value:
114
+ key, value = set_value
115
+ try:
116
+ cfg.set(key, value)
117
+ cfg.save()
118
+ click.echo(f"Set {key} = {value}")
119
+ except ValueError as e:
120
+ click.echo(f"Error: {str(e)}", err=True)
121
+ raise SystemExit(1)
122
+
123
+ if set_default_model:
124
+ valid_models = ["claude", "gpt-4", "gpt-3.5", "gemini"]
125
+ if set_default_model not in valid_models:
126
+ click.echo(f"Error: Invalid model. Choose from: {', '.join(valid_models)}", err=True)
127
+ raise SystemExit(1)
128
+ cfg.set("default_model", set_default_model)
129
+ cfg.save()
130
+ click.echo(f"Set default_model = {set_default_model}")
131
+
132
+
133
+ if __name__ == "__main__":
134
+ main()
@@ -0,0 +1,143 @@
1
+ """Configuration management for API keys, defaults, and budgets."""
2
+
3
+ import yaml
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+
7
+
8
+ class Config:
9
+ """Manage configuration file with API keys and preferences."""
10
+
11
+ DEFAULT_CONFIG = {
12
+ "anthropic_api_key": "",
13
+ "openai_api_key": "",
14
+ "google_api_key": "",
15
+ "default_model": "claude",
16
+ "daily_budget": 10.0,
17
+ "max_parallel_agents": 5
18
+ }
19
+
20
+ def __init__(self, config_path: Path, data: dict):
21
+ """Initialize configuration.
22
+
23
+ Args:
24
+ config_path: Path to config file
25
+ data: Configuration dictionary
26
+ """
27
+ self.config_path = config_path
28
+ self.data = data
29
+
30
+ @classmethod
31
+ def load(cls, config_path: Optional[str] = None) -> "Config":
32
+ """Load configuration from file.
33
+
34
+ Args:
35
+ config_path: Optional custom config path
36
+
37
+ Returns:
38
+ Config instance
39
+
40
+ Raises:
41
+ FileNotFoundError: If config file doesn't exist
42
+ """
43
+ if config_path:
44
+ path = Path(config_path)
45
+ else:
46
+ path = Path.home() / ".codex-router" / "config.yaml"
47
+
48
+ if not path.exists():
49
+ raise FileNotFoundError(f"Config not found: {path}")
50
+
51
+ try:
52
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
53
+ except yaml.YAMLError as e:
54
+ raise ValueError(f"Invalid YAML in config: {str(e)}")
55
+
56
+ if not isinstance(data, dict):
57
+ raise ValueError("Config must be a YAML dictionary")
58
+
59
+ return cls(path, data)
60
+
61
+ @classmethod
62
+ def init_default(cls, config_path: Optional[str] = None) -> "Config":
63
+ """Initialize config with default values.
64
+
65
+ Args:
66
+ config_path: Optional custom config path
67
+
68
+ Returns:
69
+ New Config instance
70
+ """
71
+ if config_path:
72
+ path = Path(config_path)
73
+ else:
74
+ path = Path.home() / ".codex-router" / "config.yaml"
75
+
76
+ path.parent.mkdir(parents=True, exist_ok=True)
77
+
78
+ data = cls.DEFAULT_CONFIG.copy()
79
+ path.write_text(yaml.dump(data, default_flow_style=False), encoding="utf-8")
80
+
81
+ return cls(path, data)
82
+
83
+ def get(self, key: str, default: Any = None) -> Any:
84
+ """Get configuration value.
85
+
86
+ Args:
87
+ key: Configuration key
88
+ default: Default value if key not found
89
+
90
+ Returns:
91
+ Configuration value
92
+ """
93
+ return self.data.get(key, default)
94
+
95
+ def set(self, key: str, value: Any) -> None:
96
+ """Set configuration value.
97
+
98
+ Args:
99
+ key: Configuration key
100
+ value: Value to set
101
+
102
+ Raises:
103
+ ValueError: If key is invalid
104
+ """
105
+ valid_keys = {
106
+ "anthropic_api_key", "openai_api_key", "google_api_key",
107
+ "default_model", "daily_budget", "max_parallel_agents"
108
+ }
109
+
110
+ if key not in valid_keys:
111
+ raise ValueError(f"Invalid config key: {key}. Valid keys: {', '.join(valid_keys)}")
112
+
113
+ if key == "daily_budget" or key == "max_parallel_agents":
114
+ try:
115
+ value = float(value) if key == "daily_budget" else int(value)
116
+ except ValueError:
117
+ raise ValueError(f"{key} must be a number")
118
+
119
+ if value < 0:
120
+ raise ValueError(f"{key} must be non-negative")
121
+
122
+ self.data[key] = value
123
+
124
+ def save(self) -> None:
125
+ """Save configuration to file."""
126
+ self.config_path.write_text(
127
+ yaml.dump(self.data, default_flow_style=False),
128
+ encoding="utf-8"
129
+ )
130
+
131
+ def validate(self) -> bool:
132
+ """Check if at least one API key is configured.
133
+
134
+ Returns:
135
+ True if valid, False otherwise
136
+ """
137
+ api_keys = [
138
+ self.data.get("anthropic_api_key"),
139
+ self.data.get("openai_api_key"),
140
+ self.data.get("google_api_key")
141
+ ]
142
+
143
+ return any(key and key.strip() for key in api_keys)