Files
mission-control-v2/backend/routers/gateway_forward.py
T
Hitonabi 86bd72838a Neue Ansicht 'Von allein': Skills + Vorschlags-Bilanz der Box (+ schlafende P1-Basis)
User-Wunsch 15.07.: EIN Viewpoint, was die Box selbst geleistet hat -
Chronik ist zu kompliziert. Neue Operativ-Ansicht #eigenleben:
- Skills aus ~/.hermes/skills (= Lucys Satz) mit Klartext-Beschreibung,
  Nutzungszaehler (.usage.json) und Herkunft (selbst erstellt / von uns
  gebaut / mitgeliefert), aufklappbar mit vollem SKILL.md.
- Vorschlags-Bilanz: angenommen (Auftragsbuch-Chronik) + abgelehnt mit
  Grund (Lern-Journal) als eine Timeline + Zaehler.
Backend: services/eigenleben.py + routers/eigenleben.py (read-only).

Mit an Bord (User-Ja, SCHLAFEND): config.V1_UPSTREAM + app.py-Weiche +
gateway_forward aus dem P1-Gateway-Auszug - ohne MC_V1_UPSTREAM in der
Unit exakt altes Verhalten. Der aktive Rest von P1 kommt separat ueber
Branch umbau/p1-gateway-auszug (dort liegt auch diese View schon).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:45:04 +02:00

68 lines
2.5 KiB
Python

"""
Dünner /v1-Roh-Weiterleiter (UMBAU v3, P1).
Wenn MC_V1_UPSTREAM gesetzt ist (Unit-Env), reicht das Steuerpult /v1 unangefasst an
den eigenständigen mc2-gateway-Prozess (Loopback :9010) durch — LAN-Clients wie die
IDE-Lane erreichen den Gateway sonst nicht. Hier passiert BEWUSST nichts: kein
Routing, keine Bild-Weiche, keine Token-Zählung — all das macht genau einmal der
Gateway-Prozess (routers/gateway_proxy.py). Rollback = Env-Zeile aus der Unit
entfernen → app.py bindet wieder den lokalen Gateway ein.
"""
import logging
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse, StreamingResponse
from config import V1_UPSTREAM
log = logging.getLogger(__name__)
router = APIRouter(prefix="/v1")
# Hop-by-hop-Header dürfen nicht blind weitergereicht werden; content-length wird von
# httpx (Request) bzw. Starlette-Chunking (Response) neu bestimmt.
_HOP_HEADERS = {
"host", "content-length", "connection", "keep-alive", "transfer-encoding",
"upgrade", "proxy-authenticate", "proxy-authorization", "te", "trailer",
}
def _clean(headers) -> dict:
return {k: v for k, v in headers.items() if k.lower() not in _HOP_HEADERS}
@router.api_route("/{path:path}", methods=["GET", "POST"])
async def forward(path: str, request: Request):
client = request.app.state.gw_client # geteilter Keep-Alive-Client (app.py lifespan)
url = f"{V1_UPSTREAM}/v1/{path}"
if request.url.query:
url = f"{url}?{request.url.query}"
body = await request.body()
req = client.build_request(
request.method, url, content=body or None, headers=_clean(request.headers),
timeout=None,
)
try:
upstream = await client.send(req, stream=True)
except Exception as exc: # Gateway-Prozess weg → ehrlicher 502 statt Hänger
log.warning("/v1-Weiterleitung an %s fehlgeschlagen: %s", V1_UPSTREAM, exc)
return JSONResponse(
{"error": {"message": f"mc2-gateway ({V1_UPSTREAM}) nicht erreichbar: {exc}",
"type": "gateway_unavailable"}},
status_code=502,
)
async def gen():
try:
async for chunk in upstream.aiter_raw():
yield chunk
finally:
await upstream.aclose()
# Streaming-Passthrough für BEIDE Fälle (SSE + normale JSON-Antwort): Starlette
# chunkt selbst, Status/Header (inkl. x-mc-routed-to) kommen vom Gateway.
return StreamingResponse(
gen(), status_code=upstream.status_code, headers=_clean(upstream.headers)
)