feat(providers): add Claude CLI subprocess transport - #6427
Conversation
Adds a new `claude_cli` transport that routes inference through the locally
installed `claude` CLI binary instead of calling api.anthropic.com directly.
This lets users with a Claude Pro/Max subscription use Hermes without a
separate API key — the CLI manages its own OAuth credentials.
Changes:
- `agent/claude_cli_adapter.py` (new): ClaudeCliAdapter with a
chat.completions-compatible interface. Tool calls use JSON-in-prompt
encoding (injects tool definitions into the system prompt and parses a
`{"tool_call": {...}}` sentinel block from the response). Multi-turn
history is preserved via <conversation_history> XML tags. Async shim
provided via asyncio.to_thread. Includes is_claude_cli_available() for
zero-cost availability check.
- `hermes_cli/providers.py`: Register "claude-cli" in HERMES_OVERLAYS with
transport="claude_cli" and auth_type="external_process". Add aliases
(claude_cli, claude-code-cli, claudecli), label override, and
"claude_cli" → "claude_cli" in TRANSPORT_TO_API_MODE.
- `hermes_cli/models.py`: Add "claude-cli" model list
(opus-4-6, sonnet-4-6, opus-4-5, sonnet-4-5, haiku-4-5, 3-7-sonnet).
- `agent/auxiliary_client.py`: Add _try_claude_cli(), append it to
_get_provider_chain() as the last-resort fallback, handle "claude-cli"
in resolve_provider_client(), and extend _to_async_client() to wrap
ClaudeCliAdapter in AsyncClaudeCliAdapter.
- `run_agent.py`: Recognise "claude_cli" as a valid api_mode, initialise
ClaudeCliAdapter in __init__ and switch_model(), and dispatch to it in
_interruptible_api_call and the streaming path (non-streaming fallback).
Installation requirement:
npm install -g @anthropic-ai/claude-code
Usage:
hermes model claude-cli # or --provider claude-cli
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Picked this up into my fork and hit three test failures worth flagging before this merges: 1. 2. Model auto-detection broken for Anthropic models — Fix: move the |
… run on current main
The cherry-picked PR was authored against an older revision of main and missed
several integration points that have since been added to run_agent.py and the
provider registry. This commit makes the claude_cli api_mode actually reachable
end-to-end and pins the CLI flag to the modern Claude Code 2.1.x option name.
Build fixes:
* hermes_cli/auth.py — register `claude-cli` in PROVIDER_REGISTRY so
resolve_provider() doesn't reject it as unknown.
* hermes_cli/runtime_provider.py — add a claude-cli branch so
resolve_runtime_provider() returns api_mode="claude_cli" instead of
silently falling back to openrouter.
* run_agent.py — add claude_cli arms to _build_api_kwargs(), the
validate_response path, the finish-reason resolver, and the main
normalize-response path. The adapter already returns OpenAI-shaped
choices, so these branches bypass the transport layer.
* agent/claude_cli_adapter.py — `claude` 2.1.139 renamed `--system` to
`--append-system-prompt`. Update the subprocess invocation.
Verified end-to-end: `hermes --provider claude-cli --model
claude-haiku-4-5-20251001 -z "Reply with exactly: PONG"` returns PONG.
Known limitation (not introduced by this commit): the adapter's
_TOOL_CALL_RE uses non-greedy `.*?` and fails to balance nested braces in
real tool-call payloads. Tool dispatch via the JSON sentinel is therefore
unreliable on real-world arguments; left for a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the substantial implementation work. This automated hermes-sweeper review is closing this under the standing
Please consider publishing this as a standalone model-provider plugin repository for installation into Closed as not-planned per standing maintainer policy ( |
Summary
claude_clitransport that routes inference through the locally installedclaudeCLI binary, letting users with a Claude Pro/Max subscription use Hermes without a separate API keyANTHROPIC_API_KEYneededMotivation
Users who have
claudeCLI installed and a Claude Pro/Max subscription have no way to use Hermes without also obtaining a separate Anthropic API key. The nativeanthropicprovider callsapi.anthropic.comdirectly and requires API credits. This transport bridges the gap by shelling out to theclaudebinary, which honours the subscription billing path — the same approach OpenClaw uses with--auth-choice claude-cli.Changes
agent/claude_cli_adapter.py(new)ClaudeCliAdapterwith achat.completions-compatible interface. Tool calls via JSON-in-prompt encoding. Async shim viaasyncio.to_thread.hermes_cli/providers.pyclaude-cliinHERMES_OVERLAYS(transport="claude_cli",auth_type="external_process"), aliases, label, andTRANSPORT_TO_API_MODEentry.hermes_cli/models.pyclaude-climodel list (opus-4-6, sonnet-4-6, haiku-4-5, etc.).agent/auxiliary_client.py_try_claude_cli(), appended to_get_provider_chain()as last-resort fallback,resolve_provider_client()branch,_to_async_client()support.run_agent.pyclaude_cliapi_mode; initialise in__init__andswitch_model; dispatch in_interruptible_api_calland streaming path.How tool calls work
The
claudeCLI's-pmode returns a final text response. To get structured tool-call decisions, tool definitions are injected into the system prompt as JSON and Claude is instructed to respond with:{"tool_call": {"name": "tool_name", "arguments": {...}}}The adapter parses this sentinel block and returns an OpenAI-compatible
tool_callsarray. This is reliable with Claude models but lacks native streaming tool events.Usage
Test plan
is_claude_cli_available()returnsTruewhenclaudeis on PATHhermes model claude-clilists available models without errorhermes --provider claude-cli chat "2+2?"tool_callspopulated in response)claude-cliwhen no other provider is configuredhermes --provider claude-cliin async auxiliary context (context compression) worksclaudeCLI is not installed🤖 Generated with Claude Code