File size: 7,490 Bytes
c2ea5ed
 
 
 
 
 
 
1670330
c2ea5ed
 
188f8a3
c2ea5ed
 
c8243d5
188f8a3
 
c8243d5
7da14b7
97068b2
c8243d5
c2ea5ed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8243d5
795b72e
c2ea5ed
 
 
 
 
 
 
 
bcbd2ec
 
 
 
 
 
 
 
 
c89d79e
c8243d5
c89d79e
bcbd2ec
c89d79e
bcbd2ec
 
c8243d5
 
59e68c5
 
 
1c1facd
 
 
fd80df3
 
 
 
 
59e68c5
 
 
 
 
 
 
1c1facd
 
 
 
 
 
59e68c5
66e834a
1c1facd
 
 
 
 
 
59e68c5
1c1facd
 
 
 
 
c2ea5ed
59e68c5
d1a53e8
 
188f8a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c2ea5ed
 
 
3b65325
 
 
c2ea5ed
c8243d5
c2ea5ed
 
 
 
 
 
 
 
 
795b72e
c2ea5ed
 
 
 
 
 
 
675e977
 
c8243d5
 
 
675e977
 
 
 
 
 
 
 
 
 
 
 
 
c8243d5
 
 
 
 
 
675e977
c2ea5ed
 
 
 
 
 
 
 
 
eb7193f
c2ea5ed
eb7193f
 
c2ea5ed
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
"""
FastAPI Application Definition
This module defines the FastAPI application and routes for the agent monitoring system.
"""

import logging
import os
import secrets
from pathlib import Path
import sys
from fastapi import FastAPI, Request, status, Depends, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
from fastapi.responses import RedirectResponse, HTMLResponse, JSONResponse
from fastapi.exception_handlers import http_exception_handler
from backend.middleware.auth import ConditionalAuthMiddleware
from backend.middleware.usage_tracker import UsageTrackingMiddleware
from backend.dependencies import require_auth_in_hf_spaces
from utils.environment import should_enable_auth, debug_environment


# Add server module to path if not already there
server_dir = os.path.dirname(os.path.abspath(__file__))
if server_dir not in sys.path:
    sys.path.append(server_dir)

# Import from backend modules
from backend.server_config import ensure_directories
from backend.routers import (
    knowledge_graphs,
    traces,
    tasks,
    temporal_graphs,
    graph_comparison,
    agentgraph,
    example_traces,
    methods,
    observability,
    auth,
    testing,
)

# Setup logging
logger = logging.getLogger("agent_monitoring_server")

# Create FastAPI app
app = FastAPI(title="Agent Monitoring System", version="1.0.0")

# Define allowed CORS origins (security fix: restrict from wildcard)
ALLOWED_ORIGINS = [
    "http://localhost:3001",
    "http://localhost:5280",
    "http://localhost:7860",
    "https://holistic-ai-agentgraph.hf.space",
    "https://huggingface.co",
]

# Add CORS middleware (first, so it's outermost)
app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
)

# IMPORTANT: Middleware runs in REVERSE order of add_middleware() calls!
# We need Session to run BEFORE Auth so that request.session is available.
# Therefore: Add Auth FIRST, then Session SECOND.

# Detect if running in HF Spaces and adjust session config accordingly
from utils.environment import is_huggingface_space

# Debug environment detection
logger.info(f"πŸ” Environment detection: is_huggingface_space() = {is_huggingface_space()}")
logger.info(f"πŸ” SPACE_ID env var: {os.getenv('SPACE_ID')}")

# Add auth middleware FIRST (will run AFTER session middleware)
app.add_middleware(ConditionalAuthMiddleware)

# Add session middleware SECOND (will run BEFORE auth middleware, setting up request.session)
# HF Spaces specific session configuration
session_secret = os.getenv("SESSION_SECRET_KEY") or secrets.token_urlsafe(32)

if is_huggingface_space():
    # HF Spaces optimized session configuration
    logger.info("πŸ—οΈ Configuring session middleware for HF Spaces environment")
    app.add_middleware(
        SessionMiddleware,
        secret_key=session_secret,
        max_age=3600,  # Shorter expiry for HF Spaces (1 hour)
        same_site="none",  # CRITICAL: Required for iframe cookies in HF Spaces
        https_only=True,  # HF Spaces uses HTTPS
    )
else:
    # Local development session configuration
    logger.info("🏠 Configuring session middleware for local development")
    app.add_middleware(
        SessionMiddleware,
        secret_key=session_secret,
        max_age=86400,  # 24 hours for local dev
        same_site="lax",  # Better for OAuth redirects
        https_only=False,  # HTTP for local dev
    )

# Add usage tracking middleware (last added = runs first, outermost)
app.add_middleware(UsageTrackingMiddleware)

# Custom exception handler for authentication redirects
@app.exception_handler(HTTPException)
async def custom_http_exception_handler(request: Request, exc: HTTPException):
    """
    Custom exception handler that redirects web requests to login page
    when they receive 401 errors with redirect_to field.
    """
    # Check if this is a 401 with redirect_to field
    if exc.status_code == 401 and isinstance(exc.detail, dict) and "redirect_to" in exc.detail:
        # Check if this is not an API request
        is_api_request = (
            request.url.path.startswith("/api/") or
            request.headers.get("accept", "").startswith("application/json") or
            request.headers.get("content-type", "").startswith("application/json")
        )
        
        if not is_api_request:
            # Redirect web requests to login page
            logger.info(f"πŸ”„ Redirecting unauthenticated user from {request.url.path} to login page")
            return RedirectResponse(url=exc.detail["redirect_to"], status_code=302)
    
    # For all other cases, use default exception handler
    return await http_exception_handler(request, exc)

# Mount datasets directory for accessing json files
app.mount("/data", StaticFiles(directory="datasets"), name="data")

# Mount static directory for papers and other static assets
app.mount("/static", StaticFiles(directory="backend/static"), name="static")

# Include routers
app.include_router(auth.router)  # Add auth router first
app.include_router(traces.router)
app.include_router(knowledge_graphs.router)
app.include_router(agentgraph.router)
app.include_router(tasks.router)
app.include_router(temporal_graphs.router)
app.include_router(graph_comparison.router)
app.include_router(example_traces.router)
app.include_router(methods.router)
app.include_router(observability.router)
app.include_router(testing.router)

# Start background scheduler for automated tasks
# scheduler_service.start()

@app.on_event("startup")
async def startup_event():
    """Start background services on app startup"""
    logger.info("βœ… Backend server starting...")
    
    # 🌍 Debug environment information
    debug_environment()
    
    # πŸ”§ Create necessary directories
    ensure_directories()
    logger.info("πŸ“ Directory structure created")
    
    # πŸ—„οΈ Initialize database on startup
    try:
        from backend.database.init_db import init_database
        init_database(reset=False, force=False)
        logger.info("πŸ—„οΈ Database initialized successfully")
    except Exception as e:
        logger.error(f"❌ Database initialization failed: {e}")
        # Don't fail startup - continue with empty database
    
    # πŸ” Log authentication status
    if should_enable_auth():
        logger.info("πŸ” Authentication ENABLED (HF Spaces environment)")
    else:
        logger.info("🏠 Authentication DISABLED (Local development)")
    
    logger.info("πŸš€ Backend API available at: http://0.0.0.0:7860")
    # scheduler_service.start() # This line is now commented out

@app.on_event("shutdown")
async def shutdown_event():
    """Stop background services on app shutdown"""
    logger.info("Stopping background services...")
    # scheduler_service.stop() # This line is now commented out


# Root route - serve React app directly (authentication handled by frontend)
@app.get("/")
async def root(request: Request):
    """Serve the React app directly - authentication is now handled by frontend"""
    return RedirectResponse(url="/agentgraph")


# Serve React app for any unmatched routes
@app.get("/app/{path:path}")
async def serve_react_app(path: str):
    """Serve the React app for client-side routing"""
    return RedirectResponse(url="/agentgraph")