tiphys.main
Tiphys entry point.
Provides the FastAPI application and CLI interface.
1""" 2Tiphys entry point. 3 4Provides the FastAPI application and CLI interface. 5""" 6 7from collections.abc import AsyncIterator 8from contextlib import asynccontextmanager 9from datetime import datetime 10from pathlib import Path 11from typing import TYPE_CHECKING 12 13import structlog 14from fastapi import FastAPI 15from fastapi.middleware.cors import CORSMiddleware 16from fastapi.responses import FileResponse 17from fastapi.staticfiles import StaticFiles 18 19from tiphys.bootstrap import create_app_context 20from tiphys.config import TiphysConfig, load_config 21from tiphys.context import AppContext, get_context 22from tiphys.logging import setup_logging 23from tiphys.server.api import router 24from tiphys.server.lock import GatewayLock, LockAcquisitionError, acquire_gateway_lock 25 26if TYPE_CHECKING: 27 pass 28 29# Static files directory 30STATIC_DIR = Path(__file__).parent / "static" 31 32logger = structlog.get_logger(__name__) 33 34# Global gateway lock (held for lifetime of process) 35_gateway_lock: GatewayLock | None = None 36 37# Startup time for uptime tracking 38_startup_time: datetime | None = None 39 40 41@asynccontextmanager 42async def lifespan(app: FastAPI) -> AsyncIterator[None]: 43 """Application lifespan handler for startup and shutdown.""" 44 global _gateway_lock, _startup_time 45 46 config = load_config() 47 48 # Setup logging 49 setup_logging(level=config.log_level, json_output=config.log_json) 50 51 # Ensure directories exist 52 config.ensure_dirs() 53 54 # Acquire gateway lock (single instance enforcement) 55 try: 56 _gateway_lock = acquire_gateway_lock(port=config.port) 57 except LockAcquisitionError as e: 58 await logger.aerror( 59 "Failed to acquire gateway lock", 60 error=str(e), 61 existing_pid=e.existing_lock.pid if e.existing_lock else None, 62 existing_port=e.existing_lock.port if e.existing_lock else None, 63 ) 64 # Re-raise to prevent startup 65 raise 66 67 # Create and initialize application context (Composition Root) 68 ctx = await create_app_context(config=config) 69 70 # Store context in app state for dependency injection 71 app.state.context = ctx 72 73 _startup_time = datetime.utcnow() 74 75 await logger.ainfo( 76 "Tiphys starting", 77 version="0.1.0", 78 host=config.host, 79 port=config.port, 80 model=config.model, 81 agent_id=config.agent_id, 82 pid=_gateway_lock.lock_info.pid if _gateway_lock.lock_info else None, 83 ) 84 85 try: 86 yield 87 finally: 88 # Shutdown application context 89 await ctx.shutdown() 90 91 # Release gateway lock 92 if _gateway_lock: 93 _gateway_lock.release() 94 _gateway_lock = None 95 96 await logger.ainfo("Tiphys shutting down") 97 98 99def create_app(config: TiphysConfig | None = None) -> FastAPI: 100 """ 101 Create and configure the FastAPI application. 102 103 Args: 104 config: Optional configuration. If not provided, loads from environment. 105 106 Returns: 107 Configured FastAPI application. 108 """ 109 if config is None: 110 config = load_config() 111 112 app = FastAPI( 113 title="Tiphys", 114 description="Local-first, extensible AI agent framework", 115 version="0.1.0", 116 lifespan=lifespan, 117 ) 118 119 # CORS middleware 120 app.add_middleware( 121 CORSMiddleware, 122 allow_origins=config.cors_origins, 123 allow_credentials=True, 124 allow_methods=["*"], 125 allow_headers=["*"], 126 ) 127 128 # Include API routes 129 app.include_router(router) 130 131 # Serve static files 132 if STATIC_DIR.exists(): 133 app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") 134 135 @app.get("/") 136 async def serve_index() -> FileResponse: 137 """Serve the main HTML page.""" 138 return FileResponse(STATIC_DIR / "index.html") 139 140 return app 141 142 143# Create the default app instance 144app = create_app() 145 146 147def cli() -> None: 148 """CLI entry point (delegates to new click CLI).""" 149 from tiphys.cli.main import cli as click_cli 150 151 click_cli(obj={}) 152 153 154def get_uptime_seconds() -> float: 155 """Get server uptime in seconds.""" 156 if _startup_time is None: 157 return 0.0 158 return (datetime.utcnow() - _startup_time).total_seconds() 159 160 161def get_startup_time() -> datetime | None: 162 """Get server startup time.""" 163 return _startup_time 164 165 166def get_app_context() -> AppContext: 167 """ 168 FastAPI dependency to get the current AppContext. 169 170 Usage in routes: 171 @app.get("/example") 172 async def example(ctx: AppContext = Depends(get_app_context)): 173 return {"agent_id": ctx.config.agent_id} 174 """ 175 return get_context() 176 177 178if __name__ == "__main__": 179 cli()
STATIC_DIR =
PosixPath('/Users/bytedance/projects/tiphys/src/tiphys/static')
logger =
<BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=('tiphys.main',))>
@asynccontextmanager
async def
lifespan(app: fastapi.applications.FastAPI) -> AsyncIterator[None]:
42@asynccontextmanager 43async def lifespan(app: FastAPI) -> AsyncIterator[None]: 44 """Application lifespan handler for startup and shutdown.""" 45 global _gateway_lock, _startup_time 46 47 config = load_config() 48 49 # Setup logging 50 setup_logging(level=config.log_level, json_output=config.log_json) 51 52 # Ensure directories exist 53 config.ensure_dirs() 54 55 # Acquire gateway lock (single instance enforcement) 56 try: 57 _gateway_lock = acquire_gateway_lock(port=config.port) 58 except LockAcquisitionError as e: 59 await logger.aerror( 60 "Failed to acquire gateway lock", 61 error=str(e), 62 existing_pid=e.existing_lock.pid if e.existing_lock else None, 63 existing_port=e.existing_lock.port if e.existing_lock else None, 64 ) 65 # Re-raise to prevent startup 66 raise 67 68 # Create and initialize application context (Composition Root) 69 ctx = await create_app_context(config=config) 70 71 # Store context in app state for dependency injection 72 app.state.context = ctx 73 74 _startup_time = datetime.utcnow() 75 76 await logger.ainfo( 77 "Tiphys starting", 78 version="0.1.0", 79 host=config.host, 80 port=config.port, 81 model=config.model, 82 agent_id=config.agent_id, 83 pid=_gateway_lock.lock_info.pid if _gateway_lock.lock_info else None, 84 ) 85 86 try: 87 yield 88 finally: 89 # Shutdown application context 90 await ctx.shutdown() 91 92 # Release gateway lock 93 if _gateway_lock: 94 _gateway_lock.release() 95 _gateway_lock = None 96 97 await logger.ainfo("Tiphys shutting down")
Application lifespan handler for startup and shutdown.
100def create_app(config: TiphysConfig | None = None) -> FastAPI: 101 """ 102 Create and configure the FastAPI application. 103 104 Args: 105 config: Optional configuration. If not provided, loads from environment. 106 107 Returns: 108 Configured FastAPI application. 109 """ 110 if config is None: 111 config = load_config() 112 113 app = FastAPI( 114 title="Tiphys", 115 description="Local-first, extensible AI agent framework", 116 version="0.1.0", 117 lifespan=lifespan, 118 ) 119 120 # CORS middleware 121 app.add_middleware( 122 CORSMiddleware, 123 allow_origins=config.cors_origins, 124 allow_credentials=True, 125 allow_methods=["*"], 126 allow_headers=["*"], 127 ) 128 129 # Include API routes 130 app.include_router(router) 131 132 # Serve static files 133 if STATIC_DIR.exists(): 134 app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") 135 136 @app.get("/") 137 async def serve_index() -> FileResponse: 138 """Serve the main HTML page.""" 139 return FileResponse(STATIC_DIR / "index.html") 140 141 return app
Create and configure the FastAPI application.
Args: config: Optional configuration. If not provided, loads from environment.
Returns: Configured FastAPI application.
app =
<fastapi.applications.FastAPI object>
def
cli() -> None:
148def cli() -> None: 149 """CLI entry point (delegates to new click CLI).""" 150 from tiphys.cli.main import cli as click_cli 151 152 click_cli(obj={})
CLI entry point (delegates to new click CLI).
def
get_uptime_seconds() -> float:
155def get_uptime_seconds() -> float: 156 """Get server uptime in seconds.""" 157 if _startup_time is None: 158 return 0.0 159 return (datetime.utcnow() - _startup_time).total_seconds()
Get server uptime in seconds.
def
get_startup_time() -> datetime.datetime | None:
162def get_startup_time() -> datetime | None: 163 """Get server startup time.""" 164 return _startup_time
Get server startup time.
167def get_app_context() -> AppContext: 168 """ 169 FastAPI dependency to get the current AppContext. 170 171 Usage in routes: 172 @app.get("/example") 173 async def example(ctx: AppContext = Depends(get_app_context)): 174 return {"agent_id": ctx.config.agent_id} 175 """ 176 return get_context()
FastAPI dependency to get the current AppContext.
Usage in routes: @app.get("/example") async def example(ctx: AppContext = Depends(get_app_context)): return {"agent_id": ctx.config.agent_id}