mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-06-30 07:15:30 +02:00
feat: 2026 UI overhaul, stability fixes, and NVIDIA NIM support
- Overhauled frontend with 2026 hacking HUD aesthetic (neon colors, glassmorphism) - Added native support for NVIDIA NIM as a Tier 2 provider - Fixed critical backend crashes in autonomous_agent.py and knowledge_processor.py - Updated Kali sandbox build to Go 1.26 and fixed health check reliability - Integrated Space Grotesk and JetBrains Mono fonts
This commit is contained in:
@@ -335,6 +335,7 @@ async def toggle_provider(provider_id: str, req: ToggleRequest):
|
||||
# Whitelist of env keys that can be modified via UI
|
||||
ALLOWED_ENV_KEYS = {
|
||||
"ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY",
|
||||
"NIM_API_KEY", "NIM_MODEL", "NIM_BASE_URL",
|
||||
"OPENROUTER_API_KEY", "TOGETHER_API_KEY", "FIREWORKS_API_KEY",
|
||||
"OLLAMA_HOST", "LMSTUDIO_HOST",
|
||||
"ENABLE_SMART_ROUTER", "ENABLE_REASONING", "ENABLE_CVE_HUNT",
|
||||
|
||||
@@ -71,6 +71,7 @@ class SettingsUpdate(BaseModel):
|
||||
llm_model: Optional[str] = None
|
||||
anthropic_api_key: Optional[str] = None
|
||||
openai_api_key: Optional[str] = None
|
||||
nim_api_key: Optional[str] = None
|
||||
openrouter_api_key: Optional[str] = None
|
||||
gemini_api_key: Optional[str] = None
|
||||
together_api_key: Optional[str] = None
|
||||
@@ -103,6 +104,7 @@ class SettingsResponse(BaseModel):
|
||||
llm_model: str = ""
|
||||
has_anthropic_key: bool = False
|
||||
has_openai_key: bool = False
|
||||
has_nim_key: bool = False
|
||||
has_openrouter_key: bool = False
|
||||
has_gemini_key: bool = False
|
||||
has_together_key: bool = False
|
||||
@@ -172,7 +174,9 @@ def _load_settings_from_env() -> dict:
|
||||
|
||||
# Detect provider from which keys are set
|
||||
provider = "claude"
|
||||
if os.getenv("ANTHROPIC_API_KEY"):
|
||||
if os.getenv("NIM_API_KEY"):
|
||||
provider = "nim"
|
||||
elif os.getenv("ANTHROPIC_API_KEY"):
|
||||
provider = "claude"
|
||||
elif os.getenv("OPENAI_API_KEY"):
|
||||
provider = "openai"
|
||||
@@ -184,6 +188,7 @@ def _load_settings_from_env() -> dict:
|
||||
"llm_model": os.getenv("DEFAULT_LLM_MODEL", ""),
|
||||
"anthropic_api_key": os.getenv("ANTHROPIC_API_KEY", ""),
|
||||
"openai_api_key": os.getenv("OPENAI_API_KEY", ""),
|
||||
"nim_api_key": os.getenv("NIM_API_KEY", ""),
|
||||
"openrouter_api_key": os.getenv("OPENROUTER_API_KEY", ""),
|
||||
"gemini_api_key": os.getenv("GEMINI_API_KEY", ""),
|
||||
"together_api_key": os.getenv("TOGETHER_API_KEY", ""),
|
||||
@@ -224,6 +229,7 @@ async def get_settings():
|
||||
llm_model=_settings.get("llm_model", ""),
|
||||
has_anthropic_key=bool(_settings["anthropic_api_key"] or os.getenv("ANTHROPIC_API_KEY")),
|
||||
has_openai_key=bool(_settings["openai_api_key"] or os.getenv("OPENAI_API_KEY")),
|
||||
has_nim_key=bool(_settings.get("nim_api_key") or os.getenv("NIM_API_KEY")),
|
||||
has_openrouter_key=bool(_settings["openrouter_api_key"] or os.getenv("OPENROUTER_API_KEY")),
|
||||
has_gemini_key=bool(_settings.get("gemini_api_key") or os.getenv("GEMINI_API_KEY")),
|
||||
has_together_key=bool(_settings.get("together_api_key") or os.getenv("TOGETHER_API_KEY")),
|
||||
@@ -275,6 +281,12 @@ async def update_settings(settings_data: SettingsUpdate):
|
||||
os.environ["OPENAI_API_KEY"] = settings_data.openai_api_key
|
||||
env_updates["OPENAI_API_KEY"] = settings_data.openai_api_key
|
||||
|
||||
if settings_data.nim_api_key is not None:
|
||||
_settings["nim_api_key"] = settings_data.nim_api_key
|
||||
if settings_data.nim_api_key:
|
||||
os.environ["NIM_API_KEY"] = settings_data.nim_api_key
|
||||
env_updates["NIM_API_KEY"] = settings_data.nim_api_key
|
||||
|
||||
if settings_data.openrouter_api_key is not None:
|
||||
_settings["openrouter_api_key"] = settings_data.openrouter_api_key
|
||||
if settings_data.openrouter_api_key:
|
||||
@@ -564,6 +576,11 @@ CLOUD_MODELS = {
|
||||
"codex": [
|
||||
{"model_id": "codex-mini-latest", "display_name": "Codex Mini", "context_length": 192000},
|
||||
],
|
||||
"nim": [
|
||||
{"model_id": "openai/gpt-oss-120b", "display_name": "NVIDIA GPT-OSS 120B", "context_length": 32768},
|
||||
{"model_id": "meta/llama-3.1-70b-instruct", "display_name": "Llama 3.1 70B (NIM)", "context_length": 32768},
|
||||
{"model_id": "meta/llama-3.1-405b-instruct", "display_name": "Llama 3.1 405B (NIM)", "context_length": 32768},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ class Settings(BaseSettings):
|
||||
# LLM Settings
|
||||
ANTHROPIC_API_KEY: Optional[str] = os.getenv("ANTHROPIC_API_KEY")
|
||||
OPENAI_API_KEY: Optional[str] = os.getenv("OPENAI_API_KEY")
|
||||
NIM_API_KEY: Optional[str] = os.getenv("NIM_API_KEY")
|
||||
NIM_BASE_URL: str = os.getenv("NIM_BASE_URL", "https://integrate.api.nvidia.com/v1/chat/completions")
|
||||
OPENROUTER_API_KEY: Optional[str] = os.getenv("OPENROUTER_API_KEY")
|
||||
GEMINI_API_KEY: Optional[str] = os.getenv("GEMINI_API_KEY")
|
||||
AZURE_OPENAI_API_KEY: Optional[str] = os.getenv("AZURE_OPENAI_API_KEY")
|
||||
|
||||
@@ -217,6 +217,8 @@ except ImportError:
|
||||
ANTHROPIC_AVAILABLE = False
|
||||
anthropic = None
|
||||
|
||||
|
||||
|
||||
# Try to import openai
|
||||
try:
|
||||
import openai
|
||||
@@ -367,6 +369,7 @@ class LLMClient:
|
||||
self.azure_openai_api_version = os.getenv("AZURE_OPENAI_API_VERSION", "2024-02-01")
|
||||
self.azure_openai_deployment = os.getenv("AZURE_OPENAI_DEPLOYMENT", "")
|
||||
self.codex_key = os.getenv("CODEX_API_KEY", "")
|
||||
self.nim_key = os.getenv("NIM_API_KEY", "")
|
||||
self.ollama_model = os.getenv("OLLAMA_MODEL", "llama3.2")
|
||||
self.configured_model = os.getenv("DEFAULT_LLM_MODEL", "") # User-configured model name
|
||||
self.client = None
|
||||
@@ -419,6 +422,14 @@ class LLMClient:
|
||||
|
||||
def _initialize_provider(self):
|
||||
"""Initialize the first available LLM provider"""
|
||||
# 0. Try NVIDIA NIM (NVIDIA's OpenAI-compatible endpoint)
|
||||
if self.nim_key:
|
||||
self.client = "nim" # Placeholder - uses HTTP requests
|
||||
self.provider = "nim"
|
||||
self.model_name = self.configured_model or os.getenv("NIM_MODEL", "openai/gpt-oss-120b")
|
||||
print(f"[LLM] NVIDIA NIM initialized (model: {self.model_name})")
|
||||
return
|
||||
|
||||
# 1. Try Claude (Anthropic)
|
||||
if ANTHROPIC_AVAILABLE and self.anthropic_key:
|
||||
try:
|
||||
@@ -573,6 +584,7 @@ class LLMClient:
|
||||
"openai_lib": OPENAI_AVAILABLE,
|
||||
"ollama_available": self._check_ollama(),
|
||||
"lmstudio_available": self._check_lmstudio(),
|
||||
"has_nim_key": bool(self.nim_key),
|
||||
"has_google_key": bool(self.google_key),
|
||||
"has_together_key": bool(self.together_key),
|
||||
"has_fireworks_key": bool(self.fireworks_key),
|
||||
@@ -639,6 +651,14 @@ class LLMClient:
|
||||
)
|
||||
return message.content[0].text
|
||||
|
||||
elif self.provider == "nim":
|
||||
return await self._generate_openai_compatible(
|
||||
prompt, system or default_system, max_tokens,
|
||||
url=os.getenv("NIM_BASE_URL", "https://integrate.api.nvidia.com/v1/chat/completions"),
|
||||
api_key=self.nim_key,
|
||||
model=self.model_name or "openai/gpt-oss-120b",
|
||||
)
|
||||
|
||||
elif self.provider == "openai":
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model_name or "gpt-4o",
|
||||
|
||||
@@ -177,7 +177,7 @@ class KnowledgeProcessor:
|
||||
|
||||
def _extract_text_pdf(self, file_path: Path) -> str:
|
||||
"""Extract text from PDF."""
|
||||
if not HAS_PYPDKF2:
|
||||
if not HAS_PYPDF2:
|
||||
logger.warning("PyPDF2 not installed - PDF extraction unavailable. Install: pip install PyPDF2")
|
||||
# Try reading as text fallback
|
||||
try:
|
||||
|
||||
@@ -77,6 +77,13 @@ class Provider:
|
||||
|
||||
# Default provider definitions
|
||||
DEFAULT_PROVIDERS: List[Dict] = [
|
||||
# === NVIDIA NIM Provider (Tier 2 - Free) ===
|
||||
{
|
||||
"id": "nim", "name": "NVIDIA NIM", "auth_type": "api_key",
|
||||
"api_format": "openai_compat", "base_url": "https://integrate.api.nvidia.com/v1",
|
||||
"tier": 2, "default_model": os.getenv("NIM_MODEL", "openai/gpt-oss-120b"),
|
||||
"env_key": "NIM_API_KEY",
|
||||
},
|
||||
# === OAuth Providers (Tier 1 - Subscription) ===
|
||||
{
|
||||
"id": "claude_code", "name": "Claude Code", "auth_type": "oauth",
|
||||
|
||||
@@ -32,7 +32,7 @@ class TokenExtractor:
|
||||
"""Extracts tokens from CLI tools installed on the system."""
|
||||
|
||||
EXTRACTORS = [
|
||||
"claude_code", "codex_cli", "gemini_cli", "cursor",
|
||||
"claude_code", "codex_cli", "cursor",
|
||||
"copilot", "iflow", "qwen_code", "kiro",
|
||||
]
|
||||
|
||||
@@ -129,46 +129,6 @@ class TokenExtractor:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _extract_gemini_cli(self) -> Optional[ExtractedToken]:
|
||||
"""Gemini CLI: ~/.gemini/oauth_creds.json"""
|
||||
creds_path = Path.home() / ".gemini" / "oauth_creds.json"
|
||||
if not creds_path.exists():
|
||||
return None
|
||||
try:
|
||||
data = json.loads(creds_path.read_text())
|
||||
access_token = data.get("access_token")
|
||||
refresh = data.get("refresh_token")
|
||||
# Gemini CLI uses multiple field names for expiry
|
||||
expires = (
|
||||
data.get("expiry_date") # Gemini CLI uses this (milliseconds)
|
||||
or data.get("expires_at")
|
||||
or data.get("expiry")
|
||||
)
|
||||
if access_token and access_token.startswith("ya29."):
|
||||
# Parse expiry: may be ms timestamp, unix timestamp, or ISO string
|
||||
if isinstance(expires, (int, float)):
|
||||
# If > 1e12, it's milliseconds — convert to seconds
|
||||
if expires > 1e12:
|
||||
expires = expires / 1000.0
|
||||
elif isinstance(expires, str):
|
||||
try:
|
||||
from datetime import datetime
|
||||
dt = datetime.fromisoformat(expires.replace("Z", "+00:00"))
|
||||
expires = dt.timestamp()
|
||||
except Exception:
|
||||
expires = None
|
||||
return ExtractedToken(
|
||||
provider_id="gemini_cli",
|
||||
credential_type="oauth",
|
||||
token=access_token,
|
||||
refresh_token=refresh,
|
||||
expires_at=expires,
|
||||
label="Gemini CLI",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def _extract_cursor(self) -> Optional[ExtractedToken]:
|
||||
"""Cursor: SQLite state.vscdb database"""
|
||||
is_mac = platform.system() == "Darwin"
|
||||
|
||||
@@ -104,8 +104,6 @@ class TokenRefresher:
|
||||
return await self._refresh_anthropic(account_id, refresh_token)
|
||||
elif provider_id == "codex_cli":
|
||||
return await self._refresh_openai(account_id, refresh_token)
|
||||
elif provider_id == "gemini_cli":
|
||||
return await self._refresh_google(account_id, refresh_token)
|
||||
else:
|
||||
logger.debug(
|
||||
f"TokenRefresher: No refresh method for {provider_id}"
|
||||
@@ -115,74 +113,6 @@ class TokenRefresher:
|
||||
logger.error(f"TokenRefresher: Refresh failed for {provider_id}: {e}")
|
||||
return False
|
||||
|
||||
async def _refresh_google(self, account_id: str, refresh_token: str) -> bool:
|
||||
"""Refresh a Google OAuth token."""
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
# Gemini CLI client_id/secret - extracted at runtime from CLI binary or set via env
|
||||
gemini_client_id = os.environ.get("GEMINI_CLI_CLIENT_ID", "")
|
||||
gemini_client_secret = os.environ.get("GEMINI_CLI_CLIENT_SECRET", "")
|
||||
if not gemini_client_id or not gemini_client_secret:
|
||||
# Try extracting from installed Gemini CLI
|
||||
try:
|
||||
from backend.core.smart_router.token_extractor import TokenExtractor
|
||||
extractor = TokenExtractor()
|
||||
creds = extractor._extract_gemini_oauth_creds()
|
||||
if creds:
|
||||
gemini_client_id = creds.get("client_id", "")
|
||||
gemini_client_secret = creds.get("client_secret", "")
|
||||
except Exception:
|
||||
pass
|
||||
payload = {
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refresh_token,
|
||||
"client_id": gemini_client_id,
|
||||
"client_secret": gemini_client_secret,
|
||||
}
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(GOOGLE_TOKEN_URL, data=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:
|
||||
if resp.status == 200:
|
||||
data = await resp.json()
|
||||
new_token = data.get("access_token")
|
||||
expires_in = data.get("expires_in", 3600)
|
||||
if new_token:
|
||||
self._registry.update_credential(
|
||||
account_id,
|
||||
new_token,
|
||||
time.time() + expires_in,
|
||||
)
|
||||
# Save refreshed token to Gemini CLI creds file
|
||||
self._save_gemini_cli_token(new_token, expires_in)
|
||||
logger.info(f"TokenRefresher: Google token refreshed (expires in {expires_in}s)")
|
||||
return True
|
||||
else:
|
||||
body = await resp.text()
|
||||
logger.warning(f"TokenRefresher: Google refresh failed: {resp.status} {body[:200]}")
|
||||
except ImportError:
|
||||
logger.warning("TokenRefresher: aiohttp not available for token refresh")
|
||||
except Exception as e:
|
||||
logger.error(f"TokenRefresher: Google refresh error: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _save_gemini_cli_token(new_token: str, expires_in: int):
|
||||
"""Write refreshed token back to Gemini CLI credentials file on disk."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
creds_path = Path.home() / ".gemini" / "oauth_creds.json"
|
||||
if not creds_path.exists():
|
||||
return
|
||||
try:
|
||||
data = json.loads(creds_path.read_text())
|
||||
data["access_token"] = new_token
|
||||
data["expiry_date"] = int((time.time() + expires_in) * 1000)
|
||||
creds_path.write_text(json.dumps(data, indent=2))
|
||||
logger.debug("TokenRefresher: Saved refreshed token to Gemini CLI creds file")
|
||||
except Exception as e:
|
||||
logger.debug(f"TokenRefresher: Failed to save Gemini CLI token: {e}")
|
||||
|
||||
async def _refresh_anthropic(self, account_id: str, refresh_token: str) -> bool:
|
||||
"""Refresh an Anthropic/Claude OAuth token."""
|
||||
try:
|
||||
|
||||
+6
-2
@@ -128,11 +128,15 @@ async def health_check():
|
||||
# Check LLM availability
|
||||
anthropic_key = os.getenv("ANTHROPIC_API_KEY", "")
|
||||
openai_key = os.getenv("OPENAI_API_KEY", "")
|
||||
nim_key = os.getenv("NIM_API_KEY", "")
|
||||
|
||||
llm_status = "not_configured"
|
||||
llm_provider = None
|
||||
|
||||
if anthropic_key and anthropic_key not in ["", "your-anthropic-api-key"]:
|
||||
if nim_key and nim_key not in ["", "your-nim-api-key"]:
|
||||
llm_status = "configured"
|
||||
llm_provider = "nim"
|
||||
elif anthropic_key and anthropic_key not in ["", "your-anthropic-api-key"]:
|
||||
llm_status = "configured"
|
||||
llm_provider = "claude"
|
||||
elif openai_key and openai_key not in ["", "your-openai-api-key"]:
|
||||
@@ -146,7 +150,7 @@ async def health_check():
|
||||
"llm": {
|
||||
"status": llm_status,
|
||||
"provider": llm_provider,
|
||||
"message": "AI agent ready" if llm_status == "configured" else "Set ANTHROPIC_API_KEY or OPENAI_API_KEY to enable AI features"
|
||||
"message": "AI agent ready" if llm_status == "configured" else "Set ANTHROPIC_API_KEY, OPENAI_API_KEY or NIM_API_KEY to enable AI features"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,3 +38,6 @@ apscheduler>=3.10.0
|
||||
httpx>=0.26.0
|
||||
pytest>=7.4.0
|
||||
pytest-asyncio>=0.23.0
|
||||
|
||||
# Document Processing
|
||||
PyPDF2>=3.0.0
|
||||
|
||||
Reference in New Issue
Block a user