mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-04-28 11:30:03 +00:00
198 lines
6.7 KiB
Python
198 lines
6.7 KiB
Python
"""FastAPI application factory and configuration."""
|
|
|
|
import os
|
|
|
|
# Opt-in to future behavior for python-telegram-bot
|
|
os.environ["PTB_TIMEDELTA"] = "1"
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from .routes import router
|
|
from .dependencies import cleanup_provider
|
|
from providers.exceptions import ProviderError
|
|
from config.settings import get_settings
|
|
|
|
# Configure logging (atomic - only on true fresh start)
|
|
_settings = get_settings()
|
|
LOG_FILE = _settings.log_file
|
|
|
|
# Check if logging is already configured (e.g., hot reload)
|
|
# If handlers exist, skip setup to avoid clearing logs mid-session
|
|
if not logging.root.handlers:
|
|
# Fresh start - clear log file and configure
|
|
open(LOG_FILE, "w", encoding="utf-8").close()
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
handlers=[logging.FileHandler(LOG_FILE, encoding="utf-8", mode="a")],
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Suppress noisy uvicorn logs
|
|
logging.getLogger("uvicorn").setLevel(logging.WARNING)
|
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
|
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan manager."""
|
|
settings = get_settings()
|
|
logger.info("Starting Claude Code Proxy...")
|
|
|
|
# Initialize messaging platform if configured
|
|
messaging_platform = None
|
|
message_handler = None
|
|
cli_manager = None
|
|
|
|
try:
|
|
# Use the messaging factory to create the right platform
|
|
from messaging.factory import create_messaging_platform
|
|
|
|
messaging_platform = create_messaging_platform(
|
|
platform_type=settings.messaging_platform,
|
|
bot_token=settings.telegram_bot_token,
|
|
allowed_user_id=settings.allowed_telegram_user_id,
|
|
)
|
|
|
|
if messaging_platform:
|
|
from messaging.handler import ClaudeMessageHandler
|
|
from messaging.session import SessionStore
|
|
from cli.manager import CLISessionManager
|
|
|
|
# Setup workspace - CLI runs in allowed_dir if set (e.g. project root)
|
|
workspace = (
|
|
os.path.abspath(settings.allowed_dir)
|
|
if settings.allowed_dir
|
|
else os.getcwd()
|
|
)
|
|
os.makedirs(workspace, exist_ok=True)
|
|
|
|
# Session data stored in agent_workspace
|
|
data_path = os.path.abspath(settings.claude_workspace)
|
|
os.makedirs(data_path, exist_ok=True)
|
|
|
|
api_url = f"http://{settings.host}:{settings.port}/v1"
|
|
allowed_dirs = [workspace] if settings.allowed_dir else []
|
|
cli_manager = CLISessionManager(
|
|
workspace_path=workspace,
|
|
api_url=api_url,
|
|
allowed_dirs=allowed_dirs,
|
|
max_sessions=settings.max_cli_sessions,
|
|
)
|
|
|
|
# Initialize session store
|
|
session_store = SessionStore(
|
|
storage_path=os.path.join(data_path, "sessions.json")
|
|
)
|
|
|
|
# Create and register message handler
|
|
message_handler = ClaudeMessageHandler(
|
|
platform=messaging_platform,
|
|
cli_manager=cli_manager,
|
|
session_store=session_store,
|
|
)
|
|
|
|
# Restore tree state if available
|
|
saved_trees = session_store.get_all_trees()
|
|
if saved_trees:
|
|
logger.info(f"Restoring {len(saved_trees)} conversation trees...")
|
|
from messaging.tree_queue import TreeQueueManager
|
|
|
|
message_handler.tree_queue = TreeQueueManager.from_dict(
|
|
{
|
|
"trees": saved_trees,
|
|
"node_to_tree": session_store.get_node_mapping(),
|
|
},
|
|
queue_update_callback=message_handler._update_queue_positions,
|
|
node_started_callback=message_handler._mark_node_processing,
|
|
)
|
|
# Reconcile restored state - anything PENDING/IN_PROGRESS is lost across restart
|
|
if message_handler.tree_queue.cleanup_stale_nodes() > 0:
|
|
# Sync back and save
|
|
tree_data = message_handler.tree_queue.to_dict()
|
|
session_store.sync_from_tree_data(
|
|
tree_data["trees"], tree_data["node_to_tree"]
|
|
)
|
|
|
|
# Wire up the handler
|
|
messaging_platform.on_message(message_handler.handle_message)
|
|
|
|
# Start the platform
|
|
await messaging_platform.start()
|
|
logger.info(
|
|
f"{messaging_platform.name} platform started with message handler"
|
|
)
|
|
|
|
except ImportError as e:
|
|
logger.warning(f"Messaging module import error: {e}")
|
|
except Exception as e:
|
|
logger.error(f"Failed to start messaging platform: {e}")
|
|
import traceback
|
|
|
|
logger.error(traceback.format_exc())
|
|
|
|
# Store in app state for access in routes
|
|
app.state.messaging_platform = messaging_platform
|
|
app.state.message_handler = message_handler
|
|
app.state.cli_manager = cli_manager
|
|
|
|
yield
|
|
|
|
# Cleanup
|
|
if messaging_platform:
|
|
await messaging_platform.stop()
|
|
if cli_manager:
|
|
await cli_manager.stop_all()
|
|
await cleanup_provider()
|
|
logger.info("Server shutting down...")
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Create and configure the FastAPI application."""
|
|
app = FastAPI(
|
|
title="Claude Code Proxy",
|
|
version="2.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# Register routes
|
|
app.include_router(router)
|
|
|
|
# Exception handlers
|
|
@app.exception_handler(ProviderError)
|
|
async def provider_error_handler(request: Request, exc: ProviderError):
|
|
"""Handle provider-specific errors and return Anthropic format."""
|
|
logger.error(f"Provider Error: {exc.error_type} - {exc.message}")
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=exc.to_anthropic_format(),
|
|
)
|
|
|
|
@app.exception_handler(Exception)
|
|
async def general_error_handler(request: Request, exc: Exception):
|
|
"""Handle general errors and return Anthropic format."""
|
|
logger.error(f"General Error: {str(exc)}")
|
|
import traceback
|
|
|
|
logger.error(traceback.format_exc())
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"type": "error",
|
|
"error": {
|
|
"type": "api_error",
|
|
"message": "An unexpected error occurred.",
|
|
},
|
|
},
|
|
)
|
|
|
|
return app
|
|
|
|
|
|
# Default app instance for uvicorn
|
|
app = create_app()
|