openhands be4fa60970 feat(telemetry): Implement M3 - Embedded Telemetry Service
Implements complete M3 milestone with TelemetryService, two-phase scheduling,
collection/upload loops, Replicated SDK integration, and FastAPI lifespan.

- TelemetryService with 3-min bootstrap and 1-hour normal intervals
- Optional Replicated SDK with fallback UUID generation
- 85 unit tests and 14 integration tests passing
- Updated design doc checklist

Co-authored-by: openhands <openhands@all-hands.dev>
2025-12-19 15:06:35 +00:00

106 lines
3.4 KiB
Python

import contextlib
import warnings
from contextlib import asynccontextmanager
from typing import AsyncIterator
from fastapi.routing import Mount
with warnings.catch_warnings():
warnings.simplefilter('ignore')
from fastapi import (
FastAPI,
Request,
)
from fastapi.responses import JSONResponse
import openhands.agenthub # noqa F401 (we import this to get the agents registered)
from openhands.app_server import v1_router
from openhands.app_server.config import get_app_lifespan_service
from openhands.integrations.service_types import AuthenticationError
from openhands.server.routes.conversation import app as conversation_api_router
from openhands.server.routes.feedback import app as feedback_api_router
from openhands.server.routes.files import app as files_api_router
from openhands.server.routes.git import app as git_api_router
from openhands.server.routes.health import add_health_endpoints
from openhands.server.routes.manage_conversations import (
app as manage_conversation_api_router,
)
from openhands.server.routes.mcp import mcp_server
from openhands.server.routes.public import app as public_api_router
from openhands.server.routes.secrets import app as secrets_router
from openhands.server.routes.security import app as security_api_router
from openhands.server.routes.settings import app as settings_router
from openhands.server.routes.trajectory import app as trajectory_router
from openhands.server.shared import conversation_manager, server_config
from openhands.server.types import AppMode
from openhands.version import get_version
mcp_app = mcp_server.http_app(path='/mcp')
def combine_lifespans(*lifespans):
# Create a combined lifespan to manage multiple session managers
@contextlib.asynccontextmanager
async def combined_lifespan(app):
async with contextlib.AsyncExitStack() as stack:
for lifespan in lifespans:
await stack.enter_async_context(lifespan(app))
yield
return combined_lifespan
@asynccontextmanager
async def _lifespan(app: FastAPI) -> AsyncIterator[None]:
async with conversation_manager:
yield
lifespans = [_lifespan, mcp_app.lifespan]
app_lifespan_ = get_app_lifespan_service()
if app_lifespan_:
lifespans.append(app_lifespan_.lifespan)
# Add telemetry lifespan for enterprise mode
try:
from enterprise.server.telemetry.lifecycle import telemetry_lifespan
lifespans.append(telemetry_lifespan)
except ImportError:
# Not running in enterprise mode, skip telemetry
pass
app = FastAPI(
title='OpenHands',
description='OpenHands: Code Less, Make More',
version=get_version(),
lifespan=combine_lifespans(*lifespans),
routes=[Mount(path='/mcp', app=mcp_app)],
)
@app.exception_handler(AuthenticationError)
async def authentication_error_handler(request: Request, exc: AuthenticationError):
return JSONResponse(
status_code=401,
content=str(exc),
)
app.include_router(public_api_router)
app.include_router(files_api_router)
app.include_router(security_api_router)
app.include_router(feedback_api_router)
app.include_router(conversation_api_router)
app.include_router(manage_conversation_api_router)
app.include_router(settings_router)
app.include_router(secrets_router)
if server_config.app_mode == AppMode.OSS:
app.include_router(git_api_router)
if server_config.enable_v1:
app.include_router(v1_router.router)
app.include_router(trajectory_router)
add_health_endpoints(app)