[t-007] Create hermes_client.py — httpx async HTTP client for Hermes #7

Open
opened 2026-07-05 08:30:59 +00:00 by david · 0 comments
Owner

Goal

Implement an httpx-based async client that proxies requests to the Hermes API server.

References

Reference

  • DESIGN.md §4 BFF services (hermes_client)
  • AGENT.md §5 File Structure Map
  • DOMAIN.md §11 Appendix: Hermes API Mapping

Implementation Steps

Implementation Steps

1. Create src/services/hermes_client.py

Create the async client with methods for all Hermes endpoints listed in DOMAIN.md §11:

# src/services/hermes_client.py
from httpx import AsyncClient, Timeout
from src.core.config import load_config

class HermesClient:
    def __init__(self):
        config = load_config()
        self.base_url = config.hermes.api_url.rstrip("/")
        self.api_key = config.hermes.api_key
        self.timeout = Timeout(30.0)

    @property
    def headers(self) -> dict:
        return {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}

    async def list_sessions(self) -> list[dict]:
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.get(f"{self.base_url}/api/sessions", headers=self.headers)
            resp.raise_for_status()
            return resp.json()

    async def get_session(self, session_id: str) -> dict | None:
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.get(f"{self.base_url}/api/sessions/{session_id}", headers=self.headers)
            if resp.status_code == 404:
                return None
            resp.raise_for_status()
            return resp.json()

    async def create_session(self) -> dict:
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.post(f"{self.base_url}/api/sessions", headers=self.headers)
            resp.raise_for_status()
            return resp.json()

    async def remove_session(self, session_id: str):
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.delete(f"{self.base_url}/api/sessions/{session_id}", headers=self.headers)
            resp.raise_for_status()

    async def rename_session(self, session_id: str, title: str):
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.patch(
                f"{self.base_url}/api/sessions/{session_id}",
                headers=self.headers,
                json={"title": title},
            )
            resp.raise_for_status()

    async def get_session_messages(self, session_id: str) -> list[dict]:
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.get(f"{self.base_url}/api/sessions/{session_id}/messages", headers=self.headers)
            resp.raise_for_status()
            return resp.json()

    async def get_models(self) -> list[dict]:
        async with AsyncClient(timeout=self.timeout) as client:
            resp = await client.get(f"{self.base_url}/v1/models", headers=self.headers)
            resp.raise_for_status()
            return resp.json()

Verification Gate

Verification Gate

Run:

cd /home/david/Projects/keryx-bff && uv run python -c "from src.services.hermes_client import HermesClient; c = HermesClient(); print(c.base_url, c.headers['Authorization'][:15])"
# Expected output: http://127.0.0.1:8642 Bearer hermes-a...

And verify methods exist:

cd /home/david/Projects/keryx-bff && uv run python -c "
from src.services.hermes_client import HermesClient
c = HermesClient()
assert hasattr(c, 'list_sessions')
assert hasattr(c, 'get_session')
assert hasattr(c, 'create_session')
assert hasattr(c, 'remove_session')
assert hasattr(c, 'rename_session')
assert hasattr(c, 'get_session_messages')
assert hasattr(c, 'get_models')
print('OK')"
# Expected output: OK

STOP Conditions

STOP Conditions

  • If any method is missing from the HermesClient class, stop and add it.

Depends on: t-004

## Goal Implement an httpx-based async client that proxies requests to the Hermes API server. ## References ## Reference - DESIGN.md §4 BFF services (hermes_client) - AGENT.md §5 File Structure Map - DOMAIN.md §11 Appendix: Hermes API Mapping ## Implementation Steps ## Implementation Steps ### 1. Create `src/services/hermes_client.py` Create the async client with methods for all Hermes endpoints listed in DOMAIN.md §11: ```python # src/services/hermes_client.py from httpx import AsyncClient, Timeout from src.core.config import load_config class HermesClient: def __init__(self): config = load_config() self.base_url = config.hermes.api_url.rstrip("/") self.api_key = config.hermes.api_key self.timeout = Timeout(30.0) @property def headers(self) -> dict: return {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"} async def list_sessions(self) -> list[dict]: async with AsyncClient(timeout=self.timeout) as client: resp = await client.get(f"{self.base_url}/api/sessions", headers=self.headers) resp.raise_for_status() return resp.json() async def get_session(self, session_id: str) -> dict | None: async with AsyncClient(timeout=self.timeout) as client: resp = await client.get(f"{self.base_url}/api/sessions/{session_id}", headers=self.headers) if resp.status_code == 404: return None resp.raise_for_status() return resp.json() async def create_session(self) -> dict: async with AsyncClient(timeout=self.timeout) as client: resp = await client.post(f"{self.base_url}/api/sessions", headers=self.headers) resp.raise_for_status() return resp.json() async def remove_session(self, session_id: str): async with AsyncClient(timeout=self.timeout) as client: resp = await client.delete(f"{self.base_url}/api/sessions/{session_id}", headers=self.headers) resp.raise_for_status() async def rename_session(self, session_id: str, title: str): async with AsyncClient(timeout=self.timeout) as client: resp = await client.patch( f"{self.base_url}/api/sessions/{session_id}", headers=self.headers, json={"title": title}, ) resp.raise_for_status() async def get_session_messages(self, session_id: str) -> list[dict]: async with AsyncClient(timeout=self.timeout) as client: resp = await client.get(f"{self.base_url}/api/sessions/{session_id}/messages", headers=self.headers) resp.raise_for_status() return resp.json() async def get_models(self) -> list[dict]: async with AsyncClient(timeout=self.timeout) as client: resp = await client.get(f"{self.base_url}/v1/models", headers=self.headers) resp.raise_for_status() return resp.json() ``` ## Verification Gate ## Verification Gate Run: ```bash cd /home/david/Projects/keryx-bff && uv run python -c "from src.services.hermes_client import HermesClient; c = HermesClient(); print(c.base_url, c.headers['Authorization'][:15])" # Expected output: http://127.0.0.1:8642 Bearer hermes-a... ``` And verify methods exist: ```bash cd /home/david/Projects/keryx-bff && uv run python -c " from src.services.hermes_client import HermesClient c = HermesClient() assert hasattr(c, 'list_sessions') assert hasattr(c, 'get_session') assert hasattr(c, 'create_session') assert hasattr(c, 'remove_session') assert hasattr(c, 'rename_session') assert hasattr(c, 'get_session_messages') assert hasattr(c, 'get_models') print('OK')" # Expected output: OK ``` ## STOP Conditions ## STOP Conditions - If any method is missing from the HermesClient class, stop and add it. Depends on: t-004
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: david/keryx-bff#7
No description provided.