Files
XiaoChen a02e3e18e7 fix(skills): fetch SkillHub rankings through the shared capped HTTP client (#1308)
`_fetch_ranking_json` was the only fetch in this module that hand-rolled
`urllib.request.urlopen` instead of calling `_http_request`.

`response.read()` therefore buffered the whole body with no size limit, where
`_http_request` checks `Content-Length` and then reads in `HTTP_READ_CHUNK`
slices up to `MAX_HTTP_BYTES` — the search path already passes a cap.

Its `except` ladder also stopped at `HTTPError`/`URLError`/`TimeoutError`, so a
mid-stream `ConnectionResetError` or an `http.client.HTTPException` escaped as a
raw exception. The endpoint that serves rankings (`routers/skills.py::
_hub_rankings`, used by `GET /skills/hub/rankings` and
`GET /agents/{id}/skills/hub/rankings`) only maps `SkillHubMarketError` to 502
and `SkillHubMarketTimeout` to 504, so an upstream hiccup became an opaque 500.

Rankings now go through `_http_request` like `_fetch_search_json` and
`_download_skillhub_package` already do. The 4 MiB JSON cap is named
`_MAX_JSON_BYTES` and used by both JSON fetches instead of repeating the literal.

`_Response` in `tests/unit/agents/test_skillhub_market.py` is the stub that
`test_fetch_ranking_json_uses_showcase_endpoint` hands back from `urlopen`, so it
has to accept the `size` argument `_http_request` passes. It now wraps the
payload in `io.BytesIO` and reads exactly like `_BytesResponse` next door, which
also makes the read loop stop on the first empty chunk instead of spinning.
2026-09-30 15:13:09 +08:00
..
2026-07-09 14:32:36 +00:00