Terminal / main.py
Baida-A
Initial clean deploy (Reverse Proxy removed)
28a08e7
Raw
History Blame
9.42 kB
"""
backend/main.py β€” Entrypoint principale dell'Agente AI con migrazione automatica.
"""
import os
import sys
import logging
import asyncio
import argparse
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
# Configurazione Logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
_logger = logging.getLogger("agente_ai.main")
app = FastAPI(
title="Agente AI API",
description="Backend per l'orchestrazione di agenti autonomi e tool-use.",
version="1.5.3",
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ── P17-F1: RLS Fix & Auto-Migration ──────────────────────────────────────────
async def _run_auto_migration():
"""Esegue la migrazione SQL per RLS e indici al boot (Z-GAP-1/2/3/4)."""
db_host = os.getenv("SUPABASE_DB_HOST")
db_pass = os.getenv("SUPABASE_DB_PASSWORD")
if not db_host or not db_pass:
_logger.warning("BOOT: Migration skipped β€” SUPABASE_DB_HOST/PASSWORD non configurati.")
return
# Lista completa dal set SENSITIVE in state.py
sensitive_keys = [
'OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'GEMINI_API_KEY', 'GROQ_API_KEY',
'HF_TOKEN', 'HUGGINGFACE_API_KEY', 'GH_TOKEN', 'GITHUB_TOKEN',
'QDRANT_API_KEY', 'DATABASE_URL', 'SESSION_SECRET', 'SECRET_KEY',
'RAILWAY_TOKEN', 'SUPABASE_KEY', 'SUPABASE_ANON_KEY',
'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID',
'CF_API_TOKEN', 'CLOUDFLARE_API_TOKEN', 'CF_ACCOUNT_ID',
'CF_API_TOKEN_B', 'CF_ACCOUNT_ID_B',
'CEREBRAS_API_KEY', 'SAMBANOVA_API_KEY',
'VAULT_KEY', 'INTERNAL_TOKEN', 'DEPLOY_SECRET', 'WEBHOOK_TOKEN',
'TERMINAL_SECRET', 'EXEC_TOKEN', 'VITE_INTERNAL_TOKEN', 'VITE_TERMINAL_SECRET',
'VITE_OPENROUTER_API_KEY', 'VITE_HF_TOKEN', 'VITE_GROQ_API_KEY',
'GH_PAGES_TOKEN', 'VERCEL_TOKEN'
]
# SAFETY: sensitive_keys Γ¨ un literal Python hardcoded β€” nessun input utente, nessun rischio injection.
keys_str = ", ".join(f"'{k}'" for k in sensitive_keys) # noqa: S608
sql = f"""
-- 1. Indexing per performance
CREATE INDEX IF NOT EXISTS idx_agent_memory_key ON public.agent_memory(key);
CREATE INDEX IF NOT EXISTS idx_agent_memory_task_id ON public.agent_memory(task_id);
-- 2. RLS Enforcement
ALTER TABLE public.agent_memory ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ai_providers ENABLE ROW LEVEL SECURITY;
-- 3. Policy: Deny Anonymous Access to sensitive keys (Full SENSITIVE set)
DROP POLICY IF EXISTS "Frontend Anon Access" ON public.agent_memory;
CREATE POLICY "Frontend Anon Access" ON public.agent_memory
FOR SELECT
USING (
auth.role() = 'anon'
AND key NOT IN ({keys_str})
);
-- 4. Policy: Full access for service_role
DROP POLICY IF EXISTS "Service Role Full Access" ON public.agent_memory;
CREATE POLICY "Service Role Full Access" ON public.agent_memory
FOR ALL
TO service_role
USING (true)
WITH CHECK (true);
-- 5. Healthcheck function
CREATE OR REPLACE FUNCTION public.health_check()
RETURNS jsonb AS $$
BEGIN
RETURN jsonb_build_object('status', 'ok', 'timestamp', now());
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
"""
try:
import psycopg2
for port in [6543, 5432]:
try:
conn = psycopg2.connect(f"postgresql://postgres:{db_pass}@{db_host}:{port}/postgres?sslmode=require", connect_timeout=5)
cur = conn.cursor()
cur.execute(sql)
conn.commit()
cur.close()
conn.close()
_logger.info(f"βœ… BOOT: Migrazione RLS completa applicata su porta {port}.")
return
except Exception as e:
_logger.debug(f"BOOT: Fallito tentativo su porta {port}: {e}")
except Exception as e:
_logger.error(f"❌ BOOT: Errore migrazione: {e}")
def _apply_rls_fix():
s_url = os.getenv("SUPABASE_URL")
s_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY") or os.getenv("SUPABASE_KEY")
if s_url and s_key:
os.environ["SUPABASE_URL"] = s_url
os.environ["SUPABASE_KEY"] = s_key
_apply_rls_fix()
# ── Importazione Route ────────────────────────────────────────────────────────
# S-GAP-FIX: Caricamento robusto dei router per evitare che un import fallito blocchi tutto.
_ROUTER_MAP = {
# ── GiΓ  montati ───────────────────────────────────────────────────────────
"state": "state",
"research": "research",
"agent_memory": "agent_memory",
"agent": "agent",
"exec": "exec",
"vault": "vault",
"browser": "browser",
"deploy": "deploy",
"scheduler": "scheduler",
"blackboard": "blackboard",
"conversations": "conversations",
"benchmark": "benchmark",
"files": "files",
"telegram": "telegram_webhook",
"marketplace": "marketplace",
"plugins": "plugins",
"skills": "skills",
"auth": "auth_managed",
# ── Aggiunti ROUTER-COMPLETE (29 moduli orfani rimontati) ─────────────────
"agent_checkpoint": "agent_checkpoint",
"agent_telemetry": "agent_telemetry",
"coding": "coding",
"daemon_status": "daemon_status",
"database": "database",
"decision_memory": "decision_memory",
"email": "email",
"event_bus": "event_bus",
"event_store": "event_store",
"gemini_vision": "gemini_vision",
"incident_registry": "incident_registry",
"integrity_manager": "integrity_manager",
"job_queue": "job_queue",
"kernel": "kernel",
"llm_cache": "llm_cache",
"mcp": "mcp",
"memory_router": "memory_router",
"notify_bot": "notify_bot",
"policy": "policy",
"providers": "providers",
"search": "search",
"semantic_cache": "semantic_cache",
"session_manager": "session_manager",
"structured_log": "structured_log",
"telemetry": "telemetry",
"terminal": "terminal",
"vision": "vision",
"web": "web",
"webhook": "webhook",
}
for prefix, module_name in _ROUTER_MAP.items():
try:
import importlib
module = importlib.import_module(f"api.{module_name}")
if hasattr(module, "router"):
app.include_router(module.router)
_logger.info(f"βœ… Route montata: /api/{prefix} (da api.{module_name})")
else:
_logger.warning(f"⚠️ Modulo api.{module_name} non ha un attributo 'router'")
except ImportError as e:
_logger.error(f"❌ Errore import rotta {prefix} (api.{module_name}): {e}")
except Exception as e:
_logger.error(f"❌ Errore montaggio rotta {prefix}: {e}")
# ── CLI Task Execution ────────────────────────────────────────────────────────
async def run_cli_task(task_description: str):
_logger.info(f"CLI: Avvio task richiesto: {task_description[:50]}...")
try:
from agents.unified_loop import UnifiedAgentLoop
from models.ai_client import AIClient
llm = AIClient()
agent = UnifiedAgentLoop(llm_client=llm)
result = await agent.run(task_description)
print("\nRESULT:\n", result)
except Exception as e:
_logger.error(f"CLI: Errore: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# ── Startup ───────────────────────────────────────────────────────────────────
@app.on_event("startup")
async def startup_event():
_logger.info("Server starting up...")
asyncio.create_task(_run_auto_migration())
if not any(arg in sys.argv for arg in ["--task", "-t"]):
try:
from api.job_queue import start_job_queue_consumer
asyncio.create_task(start_job_queue_consumer())
except Exception: pass
# ── SPA Hosting ───────────────────────────────────────────────────────────────
_STATIC_DIR = os.getenv('FRONTEND_DIST', '/app/backend/static')
if os.path.isdir(_STATIC_DIR):
app.mount('/', StaticFiles(directory=_STATIC_DIR, html=True), name='spa')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Agente AI Backend & CLI")
parser.add_argument("--task", "-t", type=str, help="Esegue un task e termina")
parser.add_argument("--port", "-p", type=int, default=8000, help="Porta server")
args = parser.parse_args()
if args.task:
asyncio.run(run_cli_task(args.task))
else:
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=args.port)