|
1 | | -"""Health check endpoint with enhanced status reporting.""" |
| 1 | +"""Health check endpoint with cached startup capability reporting.""" |
2 | 2 |
|
3 | 3 | import time |
| 4 | +from dataclasses import dataclass |
4 | 5 |
|
5 | | -from fastapi import APIRouter, Request |
| 6 | +from fastapi import APIRouter, FastAPI, Request |
6 | 7 |
|
7 | 8 | from app.models import HealthResponse |
8 | 9 |
|
9 | 10 | router = APIRouter() |
10 | 11 |
|
11 | 12 | SIDECAR_VERSION = "0.1.0" |
| 13 | +_HEALTH_CAPABILITIES_STATE_KEY = "health_capabilities" |
| 14 | + |
| 15 | + |
| 16 | +@dataclass(frozen=True) |
| 17 | +class HealthCapabilities: |
| 18 | + """Static Sidecar capabilities determined once during application startup.""" |
| 19 | + |
| 20 | + agent_ready: bool |
| 21 | + capabilities: tuple[str, ...] |
12 | 22 |
|
13 | 23 |
|
14 | 24 | def _agent_module_ready() -> bool: |
@@ -47,27 +57,45 @@ def _powermem_available() -> bool: |
47 | 57 | return False |
48 | 58 |
|
49 | 59 |
|
| 60 | +def detect_health_capabilities() -> HealthCapabilities: |
| 61 | + """Inspect optional integrations once; installed packages do not change at runtime.""" |
| 62 | + agent_ready = _agent_module_ready() and (_deepagents_available() or _langgraph_available()) |
| 63 | + capabilities: list[str] = ["health", "info"] |
| 64 | + if agent_ready: |
| 65 | + capabilities.extend(["agent", "agent_stream"]) |
| 66 | + if _powermem_available(): |
| 67 | + capabilities.append("memory") |
| 68 | + return HealthCapabilities(agent_ready=agent_ready, capabilities=tuple(capabilities)) |
| 69 | + |
| 70 | + |
| 71 | +def cache_health_capabilities(application: FastAPI) -> HealthCapabilities: |
| 72 | + """Populate the application-level health capability snapshot.""" |
| 73 | + capabilities = detect_health_capabilities() |
| 74 | + setattr(application.state, _HEALTH_CAPABILITIES_STATE_KEY, capabilities) |
| 75 | + return capabilities |
| 76 | + |
| 77 | + |
| 78 | +def get_cached_health_capabilities(application: FastAPI) -> HealthCapabilities: |
| 79 | + """Read the startup snapshot, with a safe fallback for direct ASGI tests.""" |
| 80 | + cached = getattr(application.state, _HEALTH_CAPABILITIES_STATE_KEY, None) |
| 81 | + if isinstance(cached, HealthCapabilities): |
| 82 | + return cached |
| 83 | + return cache_health_capabilities(application) |
| 84 | + |
| 85 | + |
50 | 86 | @router.get("/health", response_model=HealthResponse) |
51 | 87 | async def health_check(request: Request) -> HealthResponse: |
52 | 88 | """Return health status with version, uptime, and capabilities.""" |
53 | 89 | startup_time: float = getattr(request.app.state, "startup_time", 0.0) |
54 | 90 | uptime = time.time() - startup_time if startup_time > 0 else 0.0 |
55 | 91 |
|
56 | | - agent_ready = _agent_module_ready() and ( |
57 | | - _deepagents_available() or _langgraph_available() |
58 | | - ) |
59 | | - capabilities: list[str] = ["health", "info"] |
60 | | - if agent_ready: |
61 | | - capabilities.append("agent") |
62 | | - capabilities.append("agent_stream") |
63 | | - if _powermem_available(): |
64 | | - capabilities.append("memory") |
| 92 | + health_capabilities = get_cached_health_capabilities(request.app) |
65 | 93 |
|
66 | 94 | return HealthResponse( |
67 | 95 | status="ok", |
68 | 96 | service="misaka-agent", |
69 | 97 | version=SIDECAR_VERSION, |
70 | 98 | uptime_seconds=round(uptime, 2), |
71 | | - capabilities=capabilities, |
72 | | - agent_ready=agent_ready, |
| 99 | + capabilities=list(health_capabilities.capabilities), |
| 100 | + agent_ready=health_capabilities.agent_ready, |
73 | 101 | ) |
0 commit comments