Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions acp_adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""ACP (Agent Client Protocol) adapter for Hermes Agent.

Provides integration with editors like Zed via the ACP protocol.
Install with: pip install hermes-agent[acp]
"""
50 changes: 50 additions & 0 deletions acp_adapter/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Authentication helpers for ACP adapter.

Checks environment / dotenv for configured API keys, reusing Hermes CLI's
existing provider detection logic.
"""

import os
import sys
from pathlib import Path


def check_auth() -> dict:
"""Check if at least one inference provider is configured.

Returns dict with:
success (bool): True if a provider is available.
error (str | None): Human-readable setup instructions on failure.
"""
# Ensure project root is importable (entry.py normally handles this,
# but be defensive).
project_root = Path(__file__).resolve().parent.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

try:
from hermes_cli.main import _has_any_provider_configured

if _has_any_provider_configured():
return {"success": True, "error": None}
except Exception:
# If we can't import the helper, fall back to a direct env-var check.
env_vars = [
"OPENROUTER_API_KEY",
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"OPENAI_BASE_URL",
]
if any(os.getenv(v) for v in env_vars):
return {"success": True, "error": None}

return {
"success": False,
"error": (
"No inference provider configured. "
"Run 'hermes setup' to configure interactively, "
"or set an API key (e.g. OPENROUTER_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY) "
"in your environment or in ~/.hermes/.env. "
"See https://github.com/NousResearch/hermes-agent#setup for all supported providers."
),
}
84 changes: 84 additions & 0 deletions acp_adapter/entry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""CLI entry point for the Hermes ACP agent server.

Launched by editors (Zed, JetBrains, etc.) or directly via ``hermes-acp``.
Communicates over stdio using JSON-RPC 2.0 as defined by the Agent Client
Protocol.

All logging is redirected to stderr so stdout remains clean for JSON-RPC.
"""

from __future__ import annotations

import asyncio
import logging
import os
import sys
from pathlib import Path

# Ensure project root is importable (covers editable installs and dev usage).
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
if str(_PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(_PROJECT_ROOT))


def _load_env() -> None:
"""Load ~/.hermes/.env via dotenv, mirroring hermes_cli.main behaviour."""
try:
from dotenv import load_dotenv
from hermes_cli.config import get_env_path

env_path = get_env_path()
if env_path.exists():
try:
load_dotenv(dotenv_path=env_path, encoding="utf-8")
except UnicodeDecodeError:
load_dotenv(dotenv_path=env_path, encoding="latin-1")
except ImportError:
# python-dotenv or hermes_cli not available — rely on shell env.
pass

# Also try project-root .env as dev fallback.
try:
from dotenv import load_dotenv as _ld

project_env = _PROJECT_ROOT / ".env"
if project_env.exists():
_ld(dotenv_path=project_env, override=False)
except ImportError:
pass


def _setup_logging() -> None:
"""Route all logging to stderr so stdout stays JSON-RPC only."""
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)
root = logging.getLogger()
root.handlers.clear()
root.addHandler(handler)
root.setLevel(logging.INFO)


async def run_acp_agent() -> None:
"""Create and run the Hermes ACP agent."""
from acp import run_agent

from acp_adapter.server import HermesACPAgent

agent = HermesACPAgent()
try:
await run_agent(agent, use_unstable_protocol=True)
finally:
agent.shutdown()


def main() -> None:
"""Entry point for the ``hermes-acp`` console script."""
_load_env()
_setup_logging()
asyncio.run(run_acp_agent())


if __name__ == "__main__":
main()
Loading