86bd72838a
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>
68 lines
2.5 KiB
Python
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)
|
|
)
|