Spaces:
Paused
Paused
Mirrowel
commited on
Commit
·
1ce8eba
1
Parent(s):
c264be0
refactor(ui): 🔨 replace console.clear with cross-platform clear_screen function
Browse filesReplaced all instances of `console.clear()` with a new `clear_screen()` helper function that uses native OS commands (`cls` for Windows, `clear` for Unix-like systems) instead of ANSI escape sequences.
- Adds `clear_screen()` function to launcher_tui.py, settings_tool.py, and credential_tool.py
- Replaces 18 instances of `console.clear()` across the codebase
- Improves terminal clearing reliability on classic Windows conhost and modern terminals (Windows Terminal, Linux, Mac)
- Removes unused anthropic_provider.py and bedrock_provider.py files
- Enhances credential_tool API key setup with better provider filtering logic to prevent duplicates
- Adds debug mode to show environment variable names in credential tool
src/proxy_app/launcher_tui.py
CHANGED
|
@@ -16,6 +16,17 @@ from dotenv import load_dotenv, set_key
|
|
| 16 |
console = Console()
|
| 17 |
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
class LauncherConfig:
|
| 20 |
"""Manages launcher_config.json (host, port, logging only)"""
|
| 21 |
|
|
@@ -262,7 +273,7 @@ class LauncherTUI:
|
|
| 262 |
|
| 263 |
def show_main_menu(self):
|
| 264 |
"""Display main menu and handle selection"""
|
| 265 |
-
|
| 266 |
|
| 267 |
# Detect all settings
|
| 268 |
settings = SettingsDetector.get_all_settings()
|
|
@@ -394,7 +405,7 @@ class LauncherTUI:
|
|
| 394 |
def show_config_menu(self):
|
| 395 |
"""Display configuration sub-menu"""
|
| 396 |
while True:
|
| 397 |
-
|
| 398 |
|
| 399 |
self.console.print(Panel.fit(
|
| 400 |
"[bold cyan]⚙️ Proxy Configuration[/bold cyan]",
|
|
@@ -455,7 +466,7 @@ class LauncherTUI:
|
|
| 455 |
|
| 456 |
def show_provider_settings_menu(self):
|
| 457 |
"""Display provider/advanced settings (read-only + launch tool)"""
|
| 458 |
-
|
| 459 |
|
| 460 |
settings = SettingsDetector.get_all_settings()
|
| 461 |
credentials = settings["credentials"]
|
|
@@ -573,7 +584,7 @@ class LauncherTUI:
|
|
| 573 |
import time
|
| 574 |
|
| 575 |
# CRITICAL: Show full loading UI to replace the 6-7 second blank wait
|
| 576 |
-
|
| 577 |
|
| 578 |
_start_time = time.time()
|
| 579 |
|
|
@@ -610,7 +621,7 @@ class LauncherTUI:
|
|
| 610 |
|
| 611 |
def show_about(self):
|
| 612 |
"""Display About page with project information"""
|
| 613 |
-
|
| 614 |
|
| 615 |
self.console.print(Panel.fit(
|
| 616 |
"[bold cyan]ℹ️ About LLM API Key Proxy[/bold cyan]",
|
|
@@ -654,7 +665,7 @@ class LauncherTUI:
|
|
| 654 |
"""Prepare and launch proxy in same window"""
|
| 655 |
# Check if forced onboarding needed
|
| 656 |
if self.needs_onboarding():
|
| 657 |
-
|
| 658 |
self.console.print(Panel(
|
| 659 |
Text.from_markup(
|
| 660 |
"⚠️ [bold yellow]Setup Required[/bold yellow]\n\n"
|
|
@@ -677,13 +688,13 @@ class LauncherTUI:
|
|
| 677 |
return
|
| 678 |
|
| 679 |
# Clear console and modify sys.argv
|
| 680 |
-
|
| 681 |
self.console.print(f"\n[bold green]🚀 Starting proxy on {self.config.config['host']}:{self.config.config['port']}...[/bold green]\n")
|
| 682 |
|
| 683 |
# Clear console again to remove the starting message before main.py shows loading details
|
| 684 |
import time
|
| 685 |
time.sleep(0.5) # Brief pause so user sees the message
|
| 686 |
-
|
| 687 |
|
| 688 |
# Reconstruct sys.argv for main.py
|
| 689 |
sys.argv = [
|
|
|
|
| 16 |
console = Console()
|
| 17 |
|
| 18 |
|
| 19 |
+
def clear_screen():
|
| 20 |
+
"""
|
| 21 |
+
Cross-platform terminal clear that works robustly on both
|
| 22 |
+
classic Windows conhost and modern terminals (Windows Terminal, Linux, Mac).
|
| 23 |
+
|
| 24 |
+
Uses native OS commands instead of ANSI escape sequences:
|
| 25 |
+
- Windows (conhost & Windows Terminal): cls
|
| 26 |
+
- Unix-like systems (Linux, Mac): clear
|
| 27 |
+
"""
|
| 28 |
+
os.system('cls' if os.name == 'nt' else 'clear')
|
| 29 |
+
|
| 30 |
class LauncherConfig:
|
| 31 |
"""Manages launcher_config.json (host, port, logging only)"""
|
| 32 |
|
|
|
|
| 273 |
|
| 274 |
def show_main_menu(self):
|
| 275 |
"""Display main menu and handle selection"""
|
| 276 |
+
clear_screen()
|
| 277 |
|
| 278 |
# Detect all settings
|
| 279 |
settings = SettingsDetector.get_all_settings()
|
|
|
|
| 405 |
def show_config_menu(self):
|
| 406 |
"""Display configuration sub-menu"""
|
| 407 |
while True:
|
| 408 |
+
clear_screen()
|
| 409 |
|
| 410 |
self.console.print(Panel.fit(
|
| 411 |
"[bold cyan]⚙️ Proxy Configuration[/bold cyan]",
|
|
|
|
| 466 |
|
| 467 |
def show_provider_settings_menu(self):
|
| 468 |
"""Display provider/advanced settings (read-only + launch tool)"""
|
| 469 |
+
clear_screen()
|
| 470 |
|
| 471 |
settings = SettingsDetector.get_all_settings()
|
| 472 |
credentials = settings["credentials"]
|
|
|
|
| 584 |
import time
|
| 585 |
|
| 586 |
# CRITICAL: Show full loading UI to replace the 6-7 second blank wait
|
| 587 |
+
clear_screen()
|
| 588 |
|
| 589 |
_start_time = time.time()
|
| 590 |
|
|
|
|
| 621 |
|
| 622 |
def show_about(self):
|
| 623 |
"""Display About page with project information"""
|
| 624 |
+
clear_screen()
|
| 625 |
|
| 626 |
self.console.print(Panel.fit(
|
| 627 |
"[bold cyan]ℹ️ About LLM API Key Proxy[/bold cyan]",
|
|
|
|
| 665 |
"""Prepare and launch proxy in same window"""
|
| 666 |
# Check if forced onboarding needed
|
| 667 |
if self.needs_onboarding():
|
| 668 |
+
clear_screen()
|
| 669 |
self.console.print(Panel(
|
| 670 |
Text.from_markup(
|
| 671 |
"⚠️ [bold yellow]Setup Required[/bold yellow]\n\n"
|
|
|
|
| 688 |
return
|
| 689 |
|
| 690 |
# Clear console and modify sys.argv
|
| 691 |
+
clear_screen()
|
| 692 |
self.console.print(f"\n[bold green]🚀 Starting proxy on {self.config.config['host']}:{self.config.config['port']}...[/bold green]\n")
|
| 693 |
|
| 694 |
# Clear console again to remove the starting message before main.py shows loading details
|
| 695 |
import time
|
| 696 |
time.sleep(0.5) # Brief pause so user sees the message
|
| 697 |
+
clear_screen()
|
| 698 |
|
| 699 |
# Reconstruct sys.argv for main.py
|
| 700 |
sys.argv = [
|
src/proxy_app/main.py
CHANGED
|
@@ -1137,7 +1137,7 @@ if __name__ == "__main__":
|
|
| 1137 |
|
| 1138 |
def show_onboarding_message():
|
| 1139 |
"""Display clear explanatory message for why onboarding is needed."""
|
| 1140 |
-
|
| 1141 |
console.print(Panel.fit(
|
| 1142 |
"[bold cyan]🚀 LLM API Key Proxy - First Time Setup[/bold cyan]",
|
| 1143 |
border_style="cyan"
|
|
|
|
| 1137 |
|
| 1138 |
def show_onboarding_message():
|
| 1139 |
"""Display clear explanatory message for why onboarding is needed."""
|
| 1140 |
+
os.system('cls' if os.name == 'nt' else 'clear') # Clear terminal for clean presentation
|
| 1141 |
console.print(Panel.fit(
|
| 1142 |
"[bold cyan]🚀 LLM API Key Proxy - First Time Setup[/bold cyan]",
|
| 1143 |
border_style="cyan"
|
src/proxy_app/settings_tool.py
CHANGED
|
@@ -15,6 +15,18 @@ from dotenv import set_key, unset_key
|
|
| 15 |
console = Console()
|
| 16 |
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
class AdvancedSettings:
|
| 19 |
"""Manages pending changes to .env"""
|
| 20 |
|
|
@@ -389,7 +401,7 @@ class SettingsTool:
|
|
| 389 |
|
| 390 |
def show_main_menu(self):
|
| 391 |
"""Display settings categories"""
|
| 392 |
-
|
| 393 |
|
| 394 |
self.console.print(Panel.fit(
|
| 395 |
"[bold cyan]🔧 Advanced Settings Configuration[/bold cyan]",
|
|
@@ -436,7 +448,7 @@ class SettingsTool:
|
|
| 436 |
def manage_custom_providers(self):
|
| 437 |
"""Manage custom provider API bases"""
|
| 438 |
while True:
|
| 439 |
-
|
| 440 |
|
| 441 |
providers = self.provider_mgr.get_current_providers()
|
| 442 |
|
|
@@ -533,7 +545,7 @@ class SettingsTool:
|
|
| 533 |
def manage_model_definitions(self):
|
| 534 |
"""Manage provider model definitions"""
|
| 535 |
while True:
|
| 536 |
-
|
| 537 |
|
| 538 |
all_providers = self.model_mgr.get_all_providers_with_models()
|
| 539 |
|
|
@@ -710,7 +722,7 @@ class SettingsTool:
|
|
| 710 |
current_models = {m: {} for m in current_models}
|
| 711 |
|
| 712 |
while True:
|
| 713 |
-
|
| 714 |
self.console.print(f"[bold]Editing models for: {provider}[/bold]\n")
|
| 715 |
self.console.print("Current models:")
|
| 716 |
for i, (name, definition) in enumerate(current_models.items(), 1):
|
|
@@ -788,7 +800,7 @@ class SettingsTool:
|
|
| 788 |
input("\nPress Enter to continue...")
|
| 789 |
return
|
| 790 |
|
| 791 |
-
|
| 792 |
self.console.print(f"[bold]Provider: {provider}[/bold]\n")
|
| 793 |
self.console.print("[bold]📦 Configured Models:[/bold]")
|
| 794 |
self.console.print("━" * 50)
|
|
@@ -816,7 +828,7 @@ class SettingsTool:
|
|
| 816 |
def manage_provider_settings(self):
|
| 817 |
"""Manage provider-specific settings (Antigravity, Gemini CLI)"""
|
| 818 |
while True:
|
| 819 |
-
|
| 820 |
|
| 821 |
available_providers = self.provider_settings_mgr.get_available_providers()
|
| 822 |
|
|
@@ -863,7 +875,7 @@ class SettingsTool:
|
|
| 863 |
def _manage_single_provider_settings(self, provider: str):
|
| 864 |
"""Manage settings for a single provider"""
|
| 865 |
while True:
|
| 866 |
-
|
| 867 |
|
| 868 |
display_name = provider.replace("_", " ").title()
|
| 869 |
definitions = self.provider_settings_mgr.get_provider_settings_definitions(provider)
|
|
@@ -1005,7 +1017,7 @@ class SettingsTool:
|
|
| 1005 |
def manage_concurrency_limits(self):
|
| 1006 |
"""Manage concurrency limits"""
|
| 1007 |
while True:
|
| 1008 |
-
|
| 1009 |
|
| 1010 |
limits = self.concurrency_mgr.get_current_limits()
|
| 1011 |
|
|
|
|
| 15 |
console = Console()
|
| 16 |
|
| 17 |
|
| 18 |
+
def clear_screen():
|
| 19 |
+
"""
|
| 20 |
+
Cross-platform terminal clear that works robustly on both
|
| 21 |
+
classic Windows conhost and modern terminals (Windows Terminal, Linux, Mac).
|
| 22 |
+
|
| 23 |
+
Uses native OS commands instead of ANSI escape sequences:
|
| 24 |
+
- Windows (conhost & Windows Terminal): cls
|
| 25 |
+
- Unix-like systems (Linux, Mac): clear
|
| 26 |
+
"""
|
| 27 |
+
os.system('cls' if os.name == 'nt' else 'clear')
|
| 28 |
+
|
| 29 |
+
|
| 30 |
class AdvancedSettings:
|
| 31 |
"""Manages pending changes to .env"""
|
| 32 |
|
|
|
|
| 401 |
|
| 402 |
def show_main_menu(self):
|
| 403 |
"""Display settings categories"""
|
| 404 |
+
clear_screen()
|
| 405 |
|
| 406 |
self.console.print(Panel.fit(
|
| 407 |
"[bold cyan]🔧 Advanced Settings Configuration[/bold cyan]",
|
|
|
|
| 448 |
def manage_custom_providers(self):
|
| 449 |
"""Manage custom provider API bases"""
|
| 450 |
while True:
|
| 451 |
+
clear_screen()
|
| 452 |
|
| 453 |
providers = self.provider_mgr.get_current_providers()
|
| 454 |
|
|
|
|
| 545 |
def manage_model_definitions(self):
|
| 546 |
"""Manage provider model definitions"""
|
| 547 |
while True:
|
| 548 |
+
clear_screen()
|
| 549 |
|
| 550 |
all_providers = self.model_mgr.get_all_providers_with_models()
|
| 551 |
|
|
|
|
| 722 |
current_models = {m: {} for m in current_models}
|
| 723 |
|
| 724 |
while True:
|
| 725 |
+
clear_screen()
|
| 726 |
self.console.print(f"[bold]Editing models for: {provider}[/bold]\n")
|
| 727 |
self.console.print("Current models:")
|
| 728 |
for i, (name, definition) in enumerate(current_models.items(), 1):
|
|
|
|
| 800 |
input("\nPress Enter to continue...")
|
| 801 |
return
|
| 802 |
|
| 803 |
+
clear_screen()
|
| 804 |
self.console.print(f"[bold]Provider: {provider}[/bold]\n")
|
| 805 |
self.console.print("[bold]📦 Configured Models:[/bold]")
|
| 806 |
self.console.print("━" * 50)
|
|
|
|
| 828 |
def manage_provider_settings(self):
|
| 829 |
"""Manage provider-specific settings (Antigravity, Gemini CLI)"""
|
| 830 |
while True:
|
| 831 |
+
clear_screen()
|
| 832 |
|
| 833 |
available_providers = self.provider_settings_mgr.get_available_providers()
|
| 834 |
|
|
|
|
| 875 |
def _manage_single_provider_settings(self, provider: str):
|
| 876 |
"""Manage settings for a single provider"""
|
| 877 |
while True:
|
| 878 |
+
clear_screen()
|
| 879 |
|
| 880 |
display_name = provider.replace("_", " ").title()
|
| 881 |
definitions = self.provider_settings_mgr.get_provider_settings_definitions(provider)
|
|
|
|
| 1017 |
def manage_concurrency_limits(self):
|
| 1018 |
"""Manage concurrency limits"""
|
| 1019 |
while True:
|
| 1020 |
+
clear_screen()
|
| 1021 |
|
| 1022 |
limits = self.concurrency_mgr.get_current_limits()
|
| 1023 |
|
src/rotator_library/credential_tool.py
CHANGED
|
@@ -37,6 +37,18 @@ def _ensure_providers_loaded():
|
|
| 37 |
return _provider_factory, _provider_plugins
|
| 38 |
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
def _get_credential_number_from_filename(filename: str) -> int:
|
| 41 |
"""
|
| 42 |
Extract credential number from filename like 'provider_oauth_1.json' -> 1
|
|
@@ -127,6 +139,9 @@ async def setup_api_key():
|
|
| 127 |
"""
|
| 128 |
console.print(Panel("[bold cyan]API Key Setup[/bold cyan]", expand=False))
|
| 129 |
|
|
|
|
|
|
|
|
|
|
| 130 |
# Verified list of LiteLLM providers with their friendly names and API key variables
|
| 131 |
LITELLM_PROVIDERS = {
|
| 132 |
"OpenAI": "OPENAI_API_KEY", "Anthropic": "ANTHROPIC_API_KEY",
|
|
@@ -162,26 +177,59 @@ async def setup_api_key():
|
|
| 162 |
"Nscale": "NSCALE_API_KEY", "Recraft": "RECRAFT_API_KEY",
|
| 163 |
"v0": "V0_API_KEY", "Vercel": "VERCEL_AI_GATEWAY_API_KEY",
|
| 164 |
"Topaz": "TOPAZ_API_KEY", "ElevenLabs": "ELEVENLABS_API_KEY",
|
| 165 |
-
"Deepgram": "DEEPGRAM_API_KEY",
|
| 166 |
"GitHub Models": "GITHUB_TOKEN", "GitHub Copilot": "GITHUB_COPILOT_API_KEY",
|
| 167 |
}
|
| 168 |
|
| 169 |
# Discover custom providers and add them to the list
|
| 170 |
-
# Note: gemini_cli
|
|
|
|
|
|
|
| 171 |
_, PROVIDER_PLUGINS = _ensure_providers_loaded()
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
}
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
combined_providers = {**LITELLM_PROVIDERS, **discovered_providers}
|
| 180 |
provider_display_list = sorted(combined_providers.keys())
|
| 181 |
|
| 182 |
provider_text = Text()
|
| 183 |
for i, provider_name in enumerate(provider_display_list):
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
console.print(Panel(provider_text, title="Available Providers for API Key", style="bold blue"))
|
| 187 |
|
|
@@ -1000,7 +1048,7 @@ async def export_credentials_submenu():
|
|
| 1000 |
Submenu for credential export options.
|
| 1001 |
"""
|
| 1002 |
while True:
|
| 1003 |
-
|
| 1004 |
console.print(Panel("[bold cyan]Export Credentials to .env[/bold cyan]", title="--- API Key Proxy ---", expand=False))
|
| 1005 |
|
| 1006 |
console.print(Panel(
|
|
@@ -1111,7 +1159,7 @@ async def main(clear_on_start=True):
|
|
| 1111 |
|
| 1112 |
while True:
|
| 1113 |
# Clear screen between menu selections for cleaner UX
|
| 1114 |
-
|
| 1115 |
console.print(Panel("[bold cyan]Interactive Credential Setup[/bold cyan]", title="--- API Key Proxy ---", expand=False))
|
| 1116 |
|
| 1117 |
console.print(Panel(
|
|
@@ -1179,7 +1227,7 @@ async def main(clear_on_start=True):
|
|
| 1179 |
elif setup_type == "2":
|
| 1180 |
await setup_api_key()
|
| 1181 |
#console.print("\n[dim]Press Enter to return to main menu...[/dim]")
|
| 1182 |
-
input()
|
| 1183 |
|
| 1184 |
elif setup_type == "3":
|
| 1185 |
await export_credentials_submenu()
|
|
@@ -1225,7 +1273,7 @@ def run_credential_tool(from_launcher=False):
|
|
| 1225 |
# If from launcher, don't clear screen at start to preserve loading messages
|
| 1226 |
try:
|
| 1227 |
asyncio.run(main(clear_on_start=not from_launcher))
|
| 1228 |
-
|
| 1229 |
except KeyboardInterrupt:
|
| 1230 |
console.print("\n[bold yellow]Exiting setup.[/bold yellow]")
|
| 1231 |
-
|
|
|
|
| 37 |
return _provider_factory, _provider_plugins
|
| 38 |
|
| 39 |
|
| 40 |
+
def clear_screen():
|
| 41 |
+
"""
|
| 42 |
+
Cross-platform terminal clear that works robustly on both
|
| 43 |
+
classic Windows conhost and modern terminals (Windows Terminal, Linux, Mac).
|
| 44 |
+
|
| 45 |
+
Uses native OS commands instead of ANSI escape sequences:
|
| 46 |
+
- Windows (conhost & Windows Terminal): cls
|
| 47 |
+
- Unix-like systems (Linux, Mac): clear
|
| 48 |
+
"""
|
| 49 |
+
os.system('cls' if os.name == 'nt' else 'clear')
|
| 50 |
+
|
| 51 |
+
|
| 52 |
def _get_credential_number_from_filename(filename: str) -> int:
|
| 53 |
"""
|
| 54 |
Extract credential number from filename like 'provider_oauth_1.json' -> 1
|
|
|
|
| 139 |
"""
|
| 140 |
console.print(Panel("[bold cyan]API Key Setup[/bold cyan]", expand=False))
|
| 141 |
|
| 142 |
+
# Debug toggle: Set to True to see env var names next to each provider
|
| 143 |
+
SHOW_ENV_VAR_NAMES = True
|
| 144 |
+
|
| 145 |
# Verified list of LiteLLM providers with their friendly names and API key variables
|
| 146 |
LITELLM_PROVIDERS = {
|
| 147 |
"OpenAI": "OPENAI_API_KEY", "Anthropic": "ANTHROPIC_API_KEY",
|
|
|
|
| 177 |
"Nscale": "NSCALE_API_KEY", "Recraft": "RECRAFT_API_KEY",
|
| 178 |
"v0": "V0_API_KEY", "Vercel": "VERCEL_AI_GATEWAY_API_KEY",
|
| 179 |
"Topaz": "TOPAZ_API_KEY", "ElevenLabs": "ELEVENLABS_API_KEY",
|
| 180 |
+
"Deepgram": "DEEPGRAM_API_KEY",
|
| 181 |
"GitHub Models": "GITHUB_TOKEN", "GitHub Copilot": "GITHUB_COPILOT_API_KEY",
|
| 182 |
}
|
| 183 |
|
| 184 |
# Discover custom providers and add them to the list
|
| 185 |
+
# Note: gemini_cli and antigravity are OAuth-only
|
| 186 |
+
# qwen_code API key support is a fallback
|
| 187 |
+
# iflow API key support is a feature
|
| 188 |
_, PROVIDER_PLUGINS = _ensure_providers_loaded()
|
| 189 |
+
|
| 190 |
+
# Build a set of environment variables already in LITELLM_PROVIDERS
|
| 191 |
+
# to avoid duplicates based on the actual API key names
|
| 192 |
+
litellm_env_vars = set(LITELLM_PROVIDERS.values())
|
| 193 |
+
|
| 194 |
+
# Providers to exclude from API key list
|
| 195 |
+
exclude_providers = {
|
| 196 |
+
'gemini_cli', # OAuth-only
|
| 197 |
+
'antigravity', # OAuth-only
|
| 198 |
+
'qwen_code', # API key is fallback, OAuth is primary - don't advertise
|
| 199 |
+
'openai_compatible', # Base class, not a real provider
|
| 200 |
}
|
| 201 |
|
| 202 |
+
discovered_providers = {}
|
| 203 |
+
for provider_key in PROVIDER_PLUGINS.keys():
|
| 204 |
+
if provider_key in exclude_providers:
|
| 205 |
+
continue
|
| 206 |
+
|
| 207 |
+
# Create environment variable name
|
| 208 |
+
env_var = provider_key.upper() + "_API_KEY"
|
| 209 |
+
|
| 210 |
+
# Check if this env var already exists in LITELLM_PROVIDERS
|
| 211 |
+
# This catches duplicates like GEMINI_API_KEY, MISTRAL_API_KEY, etc.
|
| 212 |
+
if env_var in litellm_env_vars:
|
| 213 |
+
# Already in LITELLM_PROVIDERS with better name, skip this one
|
| 214 |
+
continue
|
| 215 |
+
|
| 216 |
+
# Create display name for this custom provider
|
| 217 |
+
display_name = provider_key.replace('_', ' ').title()
|
| 218 |
+
discovered_providers[display_name] = env_var
|
| 219 |
+
|
| 220 |
+
# LITELLM_PROVIDERS takes precedence (comes first in merge)
|
| 221 |
combined_providers = {**LITELLM_PROVIDERS, **discovered_providers}
|
| 222 |
provider_display_list = sorted(combined_providers.keys())
|
| 223 |
|
| 224 |
provider_text = Text()
|
| 225 |
for i, provider_name in enumerate(provider_display_list):
|
| 226 |
+
if SHOW_ENV_VAR_NAMES:
|
| 227 |
+
# Extract env var prefix (before _API_KEY)
|
| 228 |
+
env_var = combined_providers[provider_name]
|
| 229 |
+
prefix = env_var.replace("_API_KEY", "").replace("_", " ")
|
| 230 |
+
provider_text.append(f" {i + 1}. {provider_name} ({prefix})\n")
|
| 231 |
+
else:
|
| 232 |
+
provider_text.append(f" {i + 1}. {provider_name}\n")
|
| 233 |
|
| 234 |
console.print(Panel(provider_text, title="Available Providers for API Key", style="bold blue"))
|
| 235 |
|
|
|
|
| 1048 |
Submenu for credential export options.
|
| 1049 |
"""
|
| 1050 |
while True:
|
| 1051 |
+
clear_screen()
|
| 1052 |
console.print(Panel("[bold cyan]Export Credentials to .env[/bold cyan]", title="--- API Key Proxy ---", expand=False))
|
| 1053 |
|
| 1054 |
console.print(Panel(
|
|
|
|
| 1159 |
|
| 1160 |
while True:
|
| 1161 |
# Clear screen between menu selections for cleaner UX
|
| 1162 |
+
clear_screen()
|
| 1163 |
console.print(Panel("[bold cyan]Interactive Credential Setup[/bold cyan]", title="--- API Key Proxy ---", expand=False))
|
| 1164 |
|
| 1165 |
console.print(Panel(
|
|
|
|
| 1227 |
elif setup_type == "2":
|
| 1228 |
await setup_api_key()
|
| 1229 |
#console.print("\n[dim]Press Enter to return to main menu...[/dim]")
|
| 1230 |
+
#input()
|
| 1231 |
|
| 1232 |
elif setup_type == "3":
|
| 1233 |
await export_credentials_submenu()
|
|
|
|
| 1273 |
# If from launcher, don't clear screen at start to preserve loading messages
|
| 1274 |
try:
|
| 1275 |
asyncio.run(main(clear_on_start=not from_launcher))
|
| 1276 |
+
clear_screen() # Clear terminal when credential tool exits
|
| 1277 |
except KeyboardInterrupt:
|
| 1278 |
console.print("\n[bold yellow]Exiting setup.[/bold yellow]")
|
| 1279 |
+
clear_screen() # Clear terminal on keyboard interrupt too
|
src/rotator_library/providers/anthropic_provider.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import logging
|
| 3 |
-
from typing import List
|
| 4 |
-
from .provider_interface import ProviderInterface
|
| 5 |
-
|
| 6 |
-
lib_logger = logging.getLogger('rotator_library')
|
| 7 |
-
lib_logger.propagate = False # Ensure this logger doesn't propagate to root
|
| 8 |
-
if not lib_logger.handlers:
|
| 9 |
-
lib_logger.addHandler(logging.NullHandler())
|
| 10 |
-
|
| 11 |
-
class AnthropicProvider(ProviderInterface):
|
| 12 |
-
"""
|
| 13 |
-
Provider implementation for the Anthropic API.
|
| 14 |
-
"""
|
| 15 |
-
async def get_models(self, api_key: str, client: httpx.AsyncClient) -> List[str]:
|
| 16 |
-
"""
|
| 17 |
-
Fetches the list of available models from the Anthropic API.
|
| 18 |
-
"""
|
| 19 |
-
try:
|
| 20 |
-
response = await client.get(
|
| 21 |
-
"https://api.anthropic.com/v1/models",
|
| 22 |
-
headers={
|
| 23 |
-
"x-api-key": api_key,
|
| 24 |
-
"anthropic-version": "2023-06-01"
|
| 25 |
-
}
|
| 26 |
-
)
|
| 27 |
-
response.raise_for_status()
|
| 28 |
-
return [f"anthropic/{model['id']}" for model in response.json().get("data", [])]
|
| 29 |
-
except httpx.RequestError as e:
|
| 30 |
-
lib_logger.error(f"Failed to fetch Anthropic models: {e}")
|
| 31 |
-
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/rotator_library/providers/bedrock_provider.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
import httpx
|
| 2 |
-
import logging
|
| 3 |
-
from typing import List
|
| 4 |
-
from .provider_interface import ProviderInterface
|
| 5 |
-
|
| 6 |
-
lib_logger = logging.getLogger('rotator_library')
|
| 7 |
-
lib_logger.propagate = False # Ensure this logger doesn't propagate to root
|
| 8 |
-
if not lib_logger.handlers:
|
| 9 |
-
lib_logger.addHandler(logging.NullHandler())
|
| 10 |
-
|
| 11 |
-
class BedrockProvider(ProviderInterface):
|
| 12 |
-
"""
|
| 13 |
-
Provider implementation for AWS Bedrock.
|
| 14 |
-
"""
|
| 15 |
-
async def get_models(self, api_key: str, client: httpx.AsyncClient) -> List[str]:
|
| 16 |
-
"""
|
| 17 |
-
Returns a hardcoded list of common Bedrock models, as there is no
|
| 18 |
-
simple, unauthenticated API endpoint to list them.
|
| 19 |
-
"""
|
| 20 |
-
# Note: Listing Bedrock models typically requires AWS credentials and boto3.
|
| 21 |
-
# For a simple, key-based proxy, we'll list common models.
|
| 22 |
-
# This can be expanded with full AWS authentication if needed.
|
| 23 |
-
lib_logger.info("Returning hardcoded list for Bedrock. Full discovery requires AWS auth.")
|
| 24 |
-
return [
|
| 25 |
-
"bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
| 26 |
-
"bedrock/anthropic.claude-3-haiku-20240307-v1:0",
|
| 27 |
-
"bedrock/cohere.command-r-plus-v1:0",
|
| 28 |
-
"bedrock/mistral.mistral-large-2402-v1:0",
|
| 29 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|