Add connectivity tests and fix Hermes handle + venv setup

- test_connectivity.py: connectivity tests for all four endpoint types
  (anthropic, openai, lm_studio, hermes, agent0) — treats no-credits as success
- test_hermes.py: raw WebSocket frame logger used to reverse-engineer protocol
- Fix handle_hermes: skip prompt.submit ack frame, read full text from
  message.complete payload.text, always raise on status==error
- Fix requirements.txt: use >= pins (fastapi/uvicorn versions didn't exist)
- Fix dev.sh: prefer python3.12 for venv (mcp>=1.9.0 requires 3.10+)
- Remove ANTHROPIC_KEY env var dependency from server.py (keys come from DB)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 19:00:51 +02:00
parent 604df52247
commit 9814d18e8c
5 changed files with 521 additions and 4 deletions
+23 -1
View File
@@ -451,21 +451,43 @@ async def handle_hermes(
log.info(f"[{agent.name}] created Hermes session {hermes_session_id}")
# ── 2. Submit prompt ──────────────────────────────────────────
await rpc("prompt.submit", {"session_id": hermes_session_id, "text": req.message})
# Send prompt.submit but do NOT wait for its ack via rpc() —
# the ack arrives interleaved with events (after message.start).
# We drain all frames below until message.complete arrives.
submit_id = f"h{req_id + 1}"
req_id += 1
await ws.send(json.dumps({
"jsonrpc": "2.0", "id": submit_id,
"method": "prompt.submit",
"params": {"session_id": hermes_session_id, "text": req.message},
}))
# ── 3. Stream events until message.complete ───────────────────
full_text = ""
while True:
raw = await asyncio.wait_for(ws.recv(), timeout=120.0)
msg = json.loads(raw)
# RPC ack for prompt.submit — ignore, keep reading
if msg.get("id") == submit_id:
continue
if msg.get("method") != "event":
continue
params = msg.get("params") or {}
etype = params.get("type", "")
payload = params.get("payload") or {}
if etype == "message.delta":
full_text += payload.get("text", "")
elif etype == "message.complete":
# payload.text holds the full assembled response
complete_text = payload.get("text", "")
if complete_text:
full_text = complete_text
if payload.get("status") == "error":
raise RuntimeError(full_text or "Hermes returned an error response")
break
elif etype == "error":
raise RuntimeError(payload.get("message", "Hermes error"))