From ba29010902ea07a2d353ffaa06bafd9212db9757 Mon Sep 17 00:00:00 2001 From: Shannon Sands Date: Sat, 6 Jun 2026 08:30:51 +1000 Subject: [PATCH] Use httpx for Telegram onboarding worker calls --- hermes_cli/web_server.py | 40 ++++++++++++++---------- tests/hermes_cli/test_web_server.py | 48 +++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 7a4703f2dbc..5e3a706dcfe 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -3385,6 +3385,7 @@ def _write_platform_enabled(platform_id: str, enabled: bool) -> None: _TELEGRAM_ONBOARDING_DEFAULT_URL = "https://setup.hermes-agent.nousresearch.com" +_TELEGRAM_ONBOARDING_USER_AGENT = f"HermesDashboard/{__version__}" _TELEGRAM_USER_ID_RE = re.compile(r"^\d+$") @@ -3457,27 +3458,32 @@ def _telegram_onboarding_request_sync( body: dict[str, Any] | None = None, bearer_token: str | None = None, ) -> dict[str, Any]: - data = None - headers = {"Accept": "application/json"} + import httpx + + headers = { + "Accept": "application/json", + "User-Agent": _TELEGRAM_ONBOARDING_USER_AGENT, + } + request_kwargs: dict[str, Any] = {} if body is not None: - data = json.dumps(body).encode("utf-8") headers["Content-Type"] = "application/json" + request_kwargs["json"] = body if bearer_token: headers["Authorization"] = f"Bearer {bearer_token}" - request = urllib.request.Request( - f"{_telegram_onboarding_base_url()}{path}", - data=data, - headers=headers, - method=method, - ) + url = f"{_telegram_onboarding_base_url()}{path}" try: - with urllib.request.urlopen(request, timeout=10) as response: - payload = response.read() - except urllib.error.HTTPError as exc: - payload = exc.read() + with httpx.Client(timeout=httpx.Timeout(10.0)) as client: + response = client.request( + method, + url, + headers=headers, + **request_kwargs, + ) + response.raise_for_status() + except httpx.HTTPStatusError as exc: try: - parsed = json.loads(payload.decode("utf-8")) + parsed = exc.response.json() except Exception: parsed = {} error = str(parsed.get("error") or parsed.get("status") or "") @@ -3485,18 +3491,18 @@ def _telegram_onboarding_request_sync( error, "Telegram setup service returned an error.", ) - status_code = 404 if exc.code == 404 else 502 + status_code = 404 if exc.response.status_code == 404 else 502 if error in {"expired", "claimed"}: status_code = 410 raise HTTPException(status_code=status_code, detail=detail) from exc - except Exception as exc: + except httpx.RequestError as exc: raise HTTPException( status_code=502, detail="Telegram setup service is unavailable. Try again shortly.", ) from exc try: - parsed = json.loads(payload.decode("utf-8")) + parsed = response.json() except Exception as exc: raise HTTPException( status_code=502, diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 11e6eb4dea0..31cf2c8f334 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -1134,6 +1134,54 @@ class TestWebServerEndpoints: assert data["state"] == "not_configured" assert "DISCORD_BOT_TOKEN" in data["message"] + def test_telegram_onboarding_worker_request_uses_httpx(self, monkeypatch): + import httpx + import hermes_cli.web_server as ws + + calls = {} + + def fail_urlopen(*_args, **_kwargs): + raise AssertionError("Telegram onboarding should not use urllib") + + class FakeHttpxClient: + def __init__(self, *args, **kwargs): + calls["client_kwargs"] = kwargs + + def __enter__(self): + return self + + def __exit__(self, *_exc_info): + return False + + def request(self, method, url, **kwargs): + calls["request"] = (method, url, kwargs) + return httpx.Response( + 201, + json={"ok": True}, + request=httpx.Request(method, url), + ) + + monkeypatch.setenv("TELEGRAM_ONBOARDING_URL", "https://worker.example") + monkeypatch.setattr(ws.urllib.request, "urlopen", fail_urlopen) + monkeypatch.setattr(httpx, "Client", FakeHttpxClient) + + payload = ws._telegram_onboarding_request_sync( + "POST", + "/v1/telegram/pairings", + body={"bot_name": "Hermes Agent"}, + bearer_token="poll-secret", + ) + + assert payload == {"ok": True} + method, url, kwargs = calls["request"] + assert method == "POST" + assert url == "https://worker.example/v1/telegram/pairings" + assert kwargs["json"] == {"bot_name": "Hermes Agent"} + assert kwargs["headers"]["Accept"] == "application/json" + assert kwargs["headers"]["Authorization"] == "Bearer poll-secret" + assert kwargs["headers"]["Content-Type"] == "application/json" + assert kwargs["headers"]["User-Agent"].startswith("HermesDashboard/") + def test_telegram_onboarding_start_strips_poll_token(self, monkeypatch): import hermes_cli.web_server as ws