| """CLI entry point for the hermes-agent ACP adapter. |
| |
| Loads environment variables from ``~/.hermes/.env``, configures logging |
| to write to stderr (so stdout is reserved for ACP JSON-RPC transport), |
| and starts the ACP agent server. |
| |
| Usage:: |
| |
| python -m acp_adapter.entry |
| # or |
| hermes acp |
| # or |
| hermes-acp |
| """ |
|
|
| import asyncio |
| import logging |
| import sys |
| from pathlib import Path |
| from hermes_constants import get_hermes_home |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| _BENIGN_PROBE_METHODS = frozenset({"ping", "health", "healthcheck"}) |
|
|
|
|
| class _BenignProbeMethodFilter(logging.Filter): |
| """Suppress acp 'Background task failed' tracebacks caused by unknown |
| liveness-probe methods (e.g. ``ping``) while leaving every other |
| background-task error — including method_not_found for any non-probe |
| method — visible in stderr. |
| """ |
|
|
| def filter(self, record: logging.LogRecord) -> bool: |
| if record.getMessage() != "Background task failed": |
| return True |
| exc_info = record.exc_info |
| if not exc_info: |
| return True |
| exc = exc_info[1] |
| |
| |
| try: |
| from acp.exceptions import RequestError |
| except ImportError: |
| return True |
| if not isinstance(exc, RequestError): |
| return True |
| if getattr(exc, "code", None) != -32601: |
| return True |
| data = getattr(exc, "data", None) |
| method = data.get("method") if isinstance(data, dict) else None |
| return method not in _BENIGN_PROBE_METHODS |
|
|
|
|
| def _setup_logging() -> None: |
| """Route all logging to stderr so stdout stays clean for ACP stdio.""" |
| handler = logging.StreamHandler(sys.stderr) |
| handler.setFormatter( |
| logging.Formatter( |
| "%(asctime)s [%(levelname)s] %(name)s: %(message)s", |
| datefmt="%Y-%m-%d %H:%M:%S", |
| ) |
| ) |
| handler.addFilter(_BenignProbeMethodFilter()) |
| root = logging.getLogger() |
| root.handlers.clear() |
| root.addHandler(handler) |
| root.setLevel(logging.INFO) |
|
|
| |
| logging.getLogger("httpx").setLevel(logging.WARNING) |
| logging.getLogger("httpcore").setLevel(logging.WARNING) |
| logging.getLogger("openai").setLevel(logging.WARNING) |
|
|
|
|
| def _load_env() -> None: |
| """Load .env from HERMES_HOME (default ``~/.hermes``).""" |
| from hermes_cli.env_loader import load_hermes_dotenv |
|
|
| hermes_home = get_hermes_home() |
| loaded = load_hermes_dotenv(hermes_home=hermes_home) |
| if loaded: |
| for env_file in loaded: |
| logging.getLogger(__name__).info("Loaded env from %s", env_file) |
| else: |
| logging.getLogger(__name__).info( |
| "No .env found at %s, using system env", hermes_home / ".env" |
| ) |
|
|
|
|
| def main() -> None: |
| """Entry point: load env, configure logging, run the ACP agent.""" |
| _setup_logging() |
| _load_env() |
|
|
| logger = logging.getLogger(__name__) |
| logger.info("Starting hermes-agent ACP adapter") |
|
|
| |
| project_root = str(Path(__file__).resolve().parent.parent) |
| if project_root not in sys.path: |
| sys.path.insert(0, project_root) |
|
|
| import acp |
| from .server import HermesACPAgent |
|
|
| agent = HermesACPAgent() |
| try: |
| asyncio.run(acp.run_agent(agent, use_unstable_protocol=True)) |
| except KeyboardInterrupt: |
| logger.info("Shutting down (KeyboardInterrupt)") |
| except Exception: |
| logger.exception("ACP agent crashed") |
| sys.exit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|