Add Hermes Agent package fallback and xAI OAuth (#808)

This commit is contained in:
ekko
2026-05-17 09:45:56 +08:00
committed by GitHub
parent 0c2bafc619
commit 53f0301da4
22 changed files with 871 additions and 26 deletions
@@ -1,7 +1,7 @@
# Agent Bridge
Optional backend-side bridge for talking to `~/.hermes/hermes-agent` by
instantiating `run_agent.AIAgent` directly in a Python process.
Optional backend-side bridge for talking to Hermes Agent by instantiating
`run_agent.AIAgent` directly in a Python process.
This is intentionally separate from the current Web UI chat path.
@@ -37,6 +37,7 @@ The service discovers Hermes Agent in this order:
3. the installed `hermes` command path
4. current working directory and parent directories
5. common locations such as `~/.hermes/hermes-agent`, `~/hermes-agent`, and `/opt/hermes-agent`
6. the `hermes-agent` package installed in the selected Python environment
Hermes home is resolved from `--hermes-home`, `HERMES_HOME`, then `~/.hermes`.
@@ -54,6 +55,12 @@ python packages/server/src/services/hermes/agent-bridge/hermes_bridge.py \
--hermes-home ~/.hermes
```
If no source checkout containing `run_agent.py` is found, the bridge falls back
to importing `run_agent` from the Python environment. This supports package
installs such as `pip install hermes-agent`. The Node manager prefers the source
checkout's virtualenv when a checkout exists, then the Python interpreter from
the installed `hermes` command, then the system Python.
The socket transport uses Python and Node standard libraries. No ZMQ dependency
is required.
@@ -11,6 +11,7 @@ from __future__ import annotations
import argparse
import copy
import importlib.util
import json
import os
import queue
@@ -116,10 +117,17 @@ def _candidate_agent_roots(raw: str | None = None) -> list[Path]:
return unique
def _discover_agent_root(raw: str | None = None) -> Path:
def _find_agent_root(raw: str | None = None) -> Path | None:
for candidate in _candidate_agent_roots(raw):
if (candidate / "run_agent.py").exists():
return candidate
return None
def _discover_agent_root(raw: str | None = None) -> Path:
root = _find_agent_root(raw)
if root is not None:
return root
attempted = ", ".join(str(path) for path in _candidate_agent_roots(raw))
raise RuntimeError(
"hermes-agent run_agent.py not found. Pass --agent-root or set "
@@ -154,8 +162,8 @@ def _jsonable(value: Any) -> Any:
return str(value)
def _agent_root() -> Path:
return _discover_agent_root(os.environ.get("HERMES_AGENT_ROOT"))
def _agent_root() -> Path | None:
return _find_agent_root(os.environ.get("HERMES_AGENT_ROOT"))
def _hermes_home() -> Path:
@@ -216,7 +224,11 @@ def _profile_dotenv_keys() -> set[str]:
def _set_path_env(agent_root: str | None = None, hermes_home: str | None = None) -> None:
os.environ["HERMES_AGENT_ROOT"] = str(_discover_agent_root(agent_root))
resolved_root = _discover_agent_root(agent_root) if agent_root else _find_agent_root()
if resolved_root is not None:
os.environ["HERMES_AGENT_ROOT"] = str(resolved_root)
else:
os.environ.pop("HERMES_AGENT_ROOT", None)
resolved_home = _discover_hermes_home(hermes_home)
os.environ["HERMES_HOME"] = str(resolved_home)
os.environ["HERMES_AGENT_BRIDGE_BASE_HOME"] = str(_normalize_base_home(resolved_home))
@@ -224,11 +236,16 @@ def _set_path_env(agent_root: str | None = None, hermes_home: str | None = None)
def _ensure_agent_imports() -> None:
root = _agent_root()
if not (root / "run_agent.py").exists():
raise RuntimeError(f"hermes-agent run_agent.py not found under {root}")
root_s = str(root)
if root_s not in sys.path:
sys.path.insert(0, root_s)
if root is not None:
root_s = str(root)
if root_s not in sys.path:
sys.path.insert(0, root_s)
elif importlib.util.find_spec("run_agent") is None:
raise RuntimeError(
"hermes-agent run_agent.py not found in source locations and the "
"current Python environment cannot import run_agent. Install "
"hermes-agent or pass --agent-root/HERMES_AGENT_ROOT."
)
os.environ.setdefault("HERMES_HOME", str(_hermes_home()))
os.environ.setdefault("HERMES_AGENT_BRIDGE_BASE_HOME", str(_hermes_home()))
@@ -47,6 +47,12 @@ function pathCandidates(agentRoot?: string): string[] {
}
function uvCandidates(agentRoot?: string): string[] {
if (!agentRoot) {
return [
process.env.HERMES_AGENT_BRIDGE_UV,
process.env.UV,
].filter((value): value is string => !!value && value.trim().length > 0)
}
return [
process.env.HERMES_AGENT_BRIDGE_UV,
process.env.UV,