scrapeRL / backend /app /api /routes /plugins.py
NeerajCodz's picture
fix: GitHub trending CSV output returns correct columns
4ece098
Raw History Blame Contribute Delete
15.7 kB
"""Plugin management API routes."""
from typing import Any
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from app.plugins.registry import (
get_all_plugins,
get_all_tools,
get_plugin,
get_plugin_summary,
get_tool,
get_tools_by_category,
PluginCategory,
)
router = APIRouter(prefix="/plugins", tags=["plugins"])
# Plugin registry - available plugins
PLUGIN_REGISTRY = {
# API Providers
"apis": [
{
"id": "openai-api",
"name": "OpenAI API",
"category": "apis",
"description": "GPT-4, GPT-4o, and embedding models",
"version": "1.0.0",
"size": "50KB",
"installed": False,
"requires_key": True,
},
{
"id": "anthropic-api",
"name": "Anthropic API",
"category": "apis",
"description": "Claude 3.5 Sonnet and Opus models",
"version": "1.0.0",
"size": "45KB",
"installed": False,
"requires_key": True,
},
{
"id": "google-api",
"name": "Google Gemini API",
"category": "apis",
"description": "Gemini Pro and Flash models",
"version": "1.0.0",
"size": "48KB",
"installed": True, # Pre-installed
"requires_key": True,
},
{
"id": "groq-api",
"name": "Groq API",
"category": "apis",
"description": "Fast inference for open models",
"version": "1.0.0",
"size": "40KB",
"installed": True, # Pre-installed
"requires_key": True,
},
{
"id": "nvidia-api",
"name": "NVIDIA API",
"category": "apis",
"description": "DeepSeek, Nemotron, and Llama models via NVIDIA",
"version": "1.0.0",
"size": "44KB",
"installed": True, # Pre-installed
"requires_key": True,
},
{
"id": "ollama-api",
"name": "Ollama (Local)",
"category": "apis",
"description": "Run models locally with Ollama",
"version": "1.0.0",
"size": "35KB",
"installed": False,
"requires_key": False,
},
],
# MCP Tools
"mcps": [
{
"id": "mcp-browser",
"name": "Browser Tools",
"category": "mcps",
"description": "Playwright-based browser automation",
"version": "1.0.0",
"size": "120KB",
"installed": True,
"requires_key": False,
},
{
"id": "mcp-search",
"name": "Search Tools",
"category": "mcps",
"description": "Google, Bing, DuckDuckGo search",
"version": "1.0.0",
"size": "80KB",
"installed": True,
"requires_key": False,
},
{
"id": "mcp-html",
"name": "HTML Processing",
"category": "mcps",
"description": "Parse, extract, and transform HTML",
"version": "1.0.0",
"size": "60KB",
"installed": True,
"requires_key": False,
},
{
"id": "mcp-python-sandbox",
"name": "Python Sandbox Executor",
"category": "mcps",
"description": "Run sandboxed Python analysis for datasets and pages",
"version": "1.0.0",
"size": "95KB",
"installed": True,
"requires_key": False,
},
{
"id": "mcp-screenshot",
"name": "Screenshot Tools",
"category": "mcps",
"description": "Capture and analyze page screenshots",
"version": "1.0.0",
"size": "90KB",
"installed": False,
"requires_key": False,
},
{
"id": "mcp-pdf",
"name": "PDF Tools",
"category": "mcps",
"description": "Extract data from PDF documents",
"version": "1.0.0",
"size": "150KB",
"installed": False,
"requires_key": False,
},
{
"id": "mcp-database",
"name": "Database Tools",
"category": "mcps",
"description": "Connect to SQL/NoSQL databases",
"version": "1.0.0",
"size": "100KB",
"installed": False,
"requires_key": False,
},
],
# Data Processors
"processors": [
{
"id": "proc-json",
"name": "JSON Processor",
"category": "processors",
"description": "Parse and transform JSON data",
"version": "1.0.0",
"size": "25KB",
"installed": True,
"requires_key": False,
},
{
"id": "proc-csv",
"name": "CSV Processor",
"category": "processors",
"description": "Parse and export CSV files",
"version": "1.0.0",
"size": "30KB",
"installed": True,
"requires_key": False,
},
{
"id": "proc-python",
"name": "Python Analysis Processor",
"category": "processors",
"description": "Execute safe Python transformations on extracted data",
"version": "1.0.0",
"size": "55KB",
"installed": True,
"requires_key": False,
},
{
"id": "proc-pandas",
"name": "Pandas Processor",
"category": "processors",
"description": "Tabular analysis and aggregation with pandas",
"version": "1.0.0",
"size": "130KB",
"installed": True,
"requires_key": False,
},
{
"id": "proc-numpy",
"name": "NumPy Processor",
"category": "processors",
"description": "Numerical analysis and statistics with NumPy",
"version": "1.0.0",
"size": "90KB",
"installed": True,
"requires_key": False,
},
{
"id": "proc-bs4",
"name": "BeautifulSoup Processor",
"category": "processors",
"description": "Advanced HTML parsing and link/content analysis via bs4",
"version": "1.0.0",
"size": "45KB",
"installed": True,
"requires_key": False,
},
{
"id": "python_sandbox",
"name": "Python Sandbox",
"category": "processors",
"description": "Execute Python code in secure sandbox environment",
"version": "1.0.0",
"size": "85KB",
"installed": True,
"requires_key": False,
},
{
"id": "proc-excel",
"name": "Excel Processor",
"category": "processors",
"description": "Read/write Excel files",
"version": "1.0.0",
"size": "500KB",
"installed": False,
"requires_key": False,
},
{
"id": "proc-xml",
"name": "XML Processor",
"category": "processors",
"description": "Parse and transform XML data",
"version": "1.0.0",
"size": "40KB",
"installed": False,
"requires_key": False,
},
],
}
# Track installed plugins (in-memory, would be persistent in production)
_installed_plugins: set[str] = {
"google-api",
"groq-api",
"nvidia-api",
"mcp-browser",
"mcp-search",
"mcp-html",
"mcp-python-sandbox",
"proc-json",
"proc-csv",
"proc-python",
"proc-pandas",
"proc-numpy",
"proc-bs4",
}
class PluginAction(BaseModel):
"""Request to install/uninstall a plugin."""
plugin_id: str = Field(..., description="Plugin identifier")
class PluginResponse(BaseModel):
"""Plugin information response."""
id: str
name: str
category: str
description: str
version: str
size: str
installed: bool
requires_key: bool
@router.get("")
@router.get("/")
async def list_plugins(category: str | None = None) -> dict[str, Any]:
"""List all available plugins, optionally filtered by category."""
result = {}
for cat, plugins in PLUGIN_REGISTRY.items():
if category and cat != category:
continue
result[cat] = [
{**plugin, "installed": plugin["id"] in _installed_plugins} for plugin in plugins
]
# Calculate stats
total = sum(len(plugins) for plugins in PLUGIN_REGISTRY.values())
installed = len(_installed_plugins)
return {
"plugins": result,
"categories": list(PLUGIN_REGISTRY.keys()),
"stats": {
"total": total,
"installed": installed,
"available": total - installed,
},
}
@router.get("/installed")
async def list_installed_plugins() -> dict[str, Any]:
"""List only installed plugins."""
installed = []
for plugins in PLUGIN_REGISTRY.values():
for plugin in plugins:
if plugin["id"] in _installed_plugins:
installed.append({**plugin, "installed": True})
return {
"plugins": installed,
"count": len(installed),
}
@router.get("/categories")
async def get_categories() -> dict[str, Any]:
"""Get plugin categories with descriptions."""
return {
"categories": [
{
"id": "apis",
"name": "API Providers",
"description": "LLM and AI service providers",
"icon": "πŸ”Œ",
},
{
"id": "mcps",
"name": "MCP Tools",
"description": "Model Context Protocol tools",
"icon": "πŸ”§",
},
{
"id": "processors",
"name": "Data Processors",
"description": "Data transformation tools",
"icon": "πŸ“Š",
},
],
}
# ==============================================================================
# Tool Registry Endpoints (must be before /{plugin_id} catch-all)
# ==============================================================================
@router.get("/tools")
async def list_tools(category: str | None = None) -> dict[str, Any]:
"""List all available tools from plugin registry."""
if category:
try:
cat = PluginCategory(category)
tools = get_tools_by_category(cat)
except ValueError:
tools = []
else:
tools = get_all_tools()
return {
"tools": [
{
"name": t.name,
"description": t.description,
"category": t.category.value,
"parameters": t.parameters,
"returns": t.returns,
}
for t in tools
],
"count": len(tools),
}
@router.get("/tools/{tool_name:path}")
async def get_tool_details(tool_name: str) -> dict[str, Any]:
"""Get details about a specific tool."""
tool = get_tool(tool_name)
if not tool:
raise HTTPException(status_code=404, detail=f"Tool not found: {tool_name}")
return {
"name": tool.name,
"description": tool.description,
"category": tool.category.value,
"parameters": tool.parameters,
"returns": tool.returns,
"examples": tool.examples,
}
@router.get("/registry")
async def get_registry_endpoint() -> dict[str, Any]:
"""Get full plugin registry with all tools."""
plugins = get_all_plugins()
return {
"plugins": [
{
"id": p.id,
"name": p.name,
"description": p.description,
"category": p.category.value,
"version": p.version,
"enabled": p.enabled,
"tools": [
{
"name": t.name,
"description": t.description,
"parameters": t.parameters,
"returns": t.returns,
}
for t in p.tools
],
"tools_count": len(p.tools),
}
for p in plugins
],
"summary": get_plugin_summary(),
}
@router.get("/summary")
async def get_summary_endpoint() -> dict[str, Any]:
"""Get summary of plugins and tools."""
return get_plugin_summary()
@router.get("/{plugin_id}")
async def get_plugin_by_id(plugin_id: str) -> PluginResponse:
"""Get details about a specific plugin."""
for plugins in PLUGIN_REGISTRY.values():
for plugin in plugins:
if plugin["id"] == plugin_id:
return PluginResponse(**{**plugin, "installed": plugin_id in _installed_plugins})
raise HTTPException(status_code=404, detail=f"Plugin not found: {plugin_id}")
@router.post("/install")
async def install_plugin(action: PluginAction) -> dict[str, Any]:
"""Install a plugin."""
plugin_id = action.plugin_id
# Find the plugin
plugin = None
for plugins in PLUGIN_REGISTRY.values():
for p in plugins:
if p["id"] == plugin_id:
plugin = p
break
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin not found: {plugin_id}")
if plugin_id in _installed_plugins:
return {
"status": "already_installed",
"message": f"Plugin {plugin['name']} is already installed",
"plugin": {**plugin, "installed": True},
}
# Install the plugin (in production, this would download/configure)
_installed_plugins.add(plugin_id)
return {
"status": "success",
"message": f"Plugin {plugin['name']} installed successfully",
"plugin": {**plugin, "installed": True},
}
@router.post("/uninstall")
async def uninstall_plugin(action: PluginAction) -> dict[str, Any]:
"""Uninstall a plugin."""
plugin_id = action.plugin_id
# Find the plugin
plugin = None
for plugins in PLUGIN_REGISTRY.values():
for p in plugins:
if p["id"] == plugin_id:
plugin = p
break
if not plugin:
raise HTTPException(status_code=404, detail=f"Plugin not found: {plugin_id}")
if plugin_id not in _installed_plugins:
return {
"status": "not_installed",
"message": f"Plugin {plugin['name']} is not installed",
"plugin": {**plugin, "installed": False},
}
# Check if it's a core plugin
core_plugins = {
"mcp-browser",
"mcp-search",
"mcp-html",
"mcp-python-sandbox",
"proc-json",
"proc-python",
"proc-pandas",
"proc-numpy",
"proc-bs4",
}
if plugin_id in core_plugins:
raise HTTPException(
status_code=400,
detail=f"Cannot uninstall core plugin: {plugin['name']}",
)
# Uninstall the plugin
_installed_plugins.discard(plugin_id)
return {
"status": "success",
"message": f"Plugin {plugin['name']} uninstalled successfully",
"plugin": {**plugin, "installed": False},
}