| """ |
| Provider status API endpoints for testing connectivity |
| |
| Handles server-side provider connectivity testing without exposing API keys to frontend. |
| """ |
|
|
| from typing import cast |
|
|
| import httpx |
| from fastapi import APIRouter, HTTPException, Path |
|
|
| from ..config.logfire_config import get_logger |
| from ..services.credential_service import credential_service |
|
|
| |
|
|
| logger = get_logger(__name__) |
| router = APIRouter(prefix="/api/providers", tags=["providers"]) |
|
|
|
|
| async def test_openai_connection(api_key: str) -> bool: |
| """Test OpenAI API connectivity""" |
| try: |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get( |
| "https://api.openai.com/v1/models", headers={"Authorization": f"Bearer {api_key}"} |
| ) |
| return bool(response.status_code == 200) |
| except Exception as e: |
| logger.warning(f"OpenAI connectivity test failed: {e}") |
| return False |
|
|
|
|
| async def test_google_connection(api_key: str) -> bool: |
| """Test Google AI API connectivity""" |
| try: |
| masked_key = f"{api_key[:4]}...{api_key[-4:]}" if api_key else "None" |
| logger.info(f"Checking Google connectivity with key: {masked_key} (len={len(api_key)})") |
|
|
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get( |
| "https://generativelanguage.googleapis.com/v1beta/models", |
| headers={"x-goog-api-key": api_key}, |
| ) |
| logger.info(f"Google API response: {response.status_code}") |
| if response.status_code != 200: |
| logger.warning(f"Google API Error Body: {response.text[:200]}") |
| return bool(response.status_code == 200) |
| except Exception as e: |
| logger.warning(f"Google AI connectivity test failed: {e}") |
| return False |
|
|
|
|
| async def test_anthropic_connection(api_key: str) -> bool: |
| """Test Anthropic API connectivity""" |
| try: |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get( |
| "https://api.anthropic.com/v1/models", headers={"x-api-key": api_key, "anthropic-version": "2023-06-01"} |
| ) |
| return bool(response.status_code == 200) |
| except Exception as e: |
| logger.warning(f"Anthropic connectivity test failed: {e}") |
| return False |
|
|
|
|
| async def test_openrouter_connection(api_key: str) -> bool: |
| """Test OpenRouter API connectivity""" |
| try: |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get( |
| "https://openrouter.ai/api/v1/models", headers={"Authorization": f"Bearer {api_key}"} |
| ) |
| return bool(response.status_code == 200) |
| except Exception as e: |
| logger.warning(f"OpenRouter connectivity test failed: {e}") |
| return False |
|
|
|
|
| async def test_grok_connection(api_key: str) -> bool: |
| """Test Grok API connectivity""" |
| try: |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get("https://api.x.ai/v1/models", headers={"Authorization": f"Bearer {api_key}"}) |
| return bool(response.status_code == 200) |
| except Exception as e: |
| logger.warning(f"Grok connectivity test failed: {e}") |
| return False |
|
|
|
|
| PROVIDER_TESTERS = { |
| "openai": test_openai_connection, |
| "google": test_google_connection, |
| "anthropic": test_anthropic_connection, |
| "openrouter": test_openrouter_connection, |
| "grok": test_grok_connection, |
| } |
|
|
|
|
| @router.get("/{provider}/status") |
| async def get_provider_status( |
| provider: str = Path( |
| ..., description="Provider name to test connectivity for", regex="^[a-z0-9_]+$", max_length=20 |
| ), |
| ): |
| """Test provider connectivity using server-side API key (secure)""" |
| try: |
| |
| allowed_providers = {"openai", "ollama", "google", "openrouter", "anthropic", "grok"} |
| if provider not in allowed_providers: |
| raise HTTPException( |
| status_code=400, detail=f"Invalid provider '{provider}'. Allowed providers: {sorted(allowed_providers)}" |
| ) |
|
|
| |
| safe_provider = provider[:20] |
| logger.info(f"Testing {safe_provider} connectivity server-side") |
|
|
| if provider not in PROVIDER_TESTERS: |
| raise HTTPException(status_code=400, detail=f"Provider '{provider}' not supported for connectivity testing") |
|
|
| |
| provider_key_map = { |
| "google": ["GEMINI_API_KEY", "GOOGLE_API_KEY"], |
| "openai": ["OPENAI_API_KEY"], |
| "anthropic": ["ANTHROPIC_API_KEY"], |
| "openrouter": ["OPENROUTER_API_KEY"], |
| "grok": ["GROK_API_KEY", "XAI_API_KEY"], |
| "ollama": [], |
| } |
|
|
| keys_to_check = provider_key_map.get(provider, []) |
| api_key = None |
|
|
| if provider == "ollama": |
| |
| |
| |
| |
| |
| |
| pass |
|
|
| for key_name in keys_to_check: |
| api_key = await credential_service.get_credential(key_name, decrypt=True) |
| if api_key and str(api_key).strip(): |
| break |
|
|
| |
| if provider != "ollama" and (not api_key or not isinstance(api_key, str) or not api_key.strip()): |
| logger.info(f"No API key configured for {safe_provider}") |
| return {"ok": False, "reason": "no_key"} |
|
|
| |
| tester = PROVIDER_TESTERS[provider] |
| is_connected = await tester(cast(str, api_key)) |
|
|
| logger.info(f"{safe_provider} connectivity test result: {is_connected}") |
| return { |
| "ok": is_connected, |
| "reason": "connected" if is_connected else "connection_failed", |
| "provider": provider, |
| } |
|
|
| except HTTPException: |
| |
| raise |
| except Exception as e: |
| |
| safe_error = str(e)[:100] |
| logger.error(f"Error testing {provider[:20]} connectivity: {safe_error}") |
| raise HTTPException(status_code=500, detail={"error": "Internal server error during connectivity test"}) from e |
|
|