Skip to content

feat: native Anthropic provider with Claude Code credential auto-discovery - #1097

Merged
teknium1 merged 11 commits into
mainfrom
hermes/hermes-c877bdeb
Mar 13, 2026
Merged

feat: native Anthropic provider with Claude Code credential auto-discovery#1097
teknium1 merged 11 commits into
mainfrom
hermes/hermes-c877bdeb

Conversation

@teknium1

@teknium1 teknium1 commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

Native Anthropic Provider with Claude Code Auto-Discovery

Adds Anthropic as a first-class inference provider using the native Anthropic SDK. 15 files, 10 commits, 43 adapter tests, 3197 total passing.

Auth (three methods, auto-resolved)

  1. ANTHROPIC_API_KEY — regular Console API key (sk-ant-api-*)
  2. ANTHROPIC_TOKEN / CLAUDE_CODE_OAUTH_TOKEN — setup-token (sk-ant-oat-*)
  3. Auto-discovery — reads ~/.claude.json (native binary v2.x) or ~/.claude/.credentials.json (legacy npm)

hermes model flow

  • Anthropic option in provider menu
  • Auto-detects Claude Code credentials if installed
  • Two-option auth choice: setup-token (with step-by-step guide) or API key
  • Live model catalog from Anthropic /v1/models endpoint (falls back to static list)

Implementation details

  • New anthropic_messages api_mode (same pattern as codex_responses)
  • Full message format translation: system extraction, tool_use/tool_result blocks, thinking/reasoning, cache_control passthrough
  • Beta headers: interleaved-thinking-2025-05-14, fine-grained-tool-streaming-2025-05-14 (all auth), plus oauth-2025-04-20 (OAuth only)
  • Adaptive thinking for 4.5+ models (uses type=adaptive), type=enabled + temperature=1 for older
  • Prompt caching auto-enabled, with native Anthropic cache metrics (cache_read_input_tokens / cache_creation_input_tokens)
  • Role alternation enforcement (Anthropic rejects consecutive same-role messages)
  • Tool call ID sanitization ([a-zA-Z0-9_-] only, per Anthropic spec)
  • Empty content guards for assistant messages and tool results
  • Malformed JSON fallback in tool_call argument parsing
  • 401 credential refresh with one-shot guard (re-reads Claude Code creds)
  • AuthError raised when no credentials found (not silent empty string)
  • Token type detection: only sk-ant-api* uses x-api-key; everything else uses Bearer auth
  • All self.client calls guarded: memory flush, summary generation, summary retry

Files changed

File What
agent/anthropic_adapter.py NEW — 466 lines: client builder, format conversion, credential reader, token resolver
tests/test_anthropic_adapter.py NEW — 43 tests
pyproject.toml anthropic>=0.39.0 dependency
hermes_cli/auth.py PROVIDER_REGISTRY + aliases
hermes_cli/models.py Model catalog, labels, aliases, order, live fetch
hermes_cli/main.py --provider choice + _model_flow_anthropic()
hermes_cli/runtime_provider.py Anthropic branch → anthropic_messages + AuthError
hermes_cli/setup.py Setup wizard with auto-detect + model selection
agent/auxiliary_client.py Claude Haiku as auxiliary model
agent/model_metadata.py Context lengths for all current Claude models
run_agent.py All anthropic_messages branches
tests/test_run_agent.py Updated + new tests
website/docs/getting-started/quickstart.md Provider table
website/docs/user-guide/configuration.md Full Anthropic section
website/docs/reference/environment-variables.md Env vars reference

Verified live

hermes model → Anthropic → auto-detected Claude Code creds → 7 live models
hermes chat --provider anthropic --model claude-opus-4-6 -q 'Say hello' → ✓

Test plan

pytest tests/test_anthropic_adapter.py -n0 -v  # 43 passed  
pytest tests/ -n0 -q                           # 3197 passed (4 pre-existing)

teknium1 added 11 commits March 12, 2026 15:47
…overy

Add Anthropic as a first-class inference provider, bypassing OpenRouter
for direct API access. Uses the native Anthropic SDK with a full format
adapter (same pattern as the codex_responses api_mode).

## Auth (three methods, priority order)
1. ANTHROPIC_API_KEY env var (regular API key, sk-ant-api-*)
2. ANTHROPIC_TOKEN / CLAUDE_CODE_OAUTH_TOKEN env var (setup-token, sk-ant-oat-*)
3. Auto-discovery from ~/.claude/.credentials.json (Claude Code subscription)
   - Reads Claude Code's OAuth credentials
   - Checks token expiry with 60s buffer
   - Setup tokens use Bearer auth + anthropic-beta: oauth-2025-04-20 header
   - Regular API keys use standard x-api-key header

## Changes by file

### New files
- agent/anthropic_adapter.py — Client builder, message/tool/response
  format conversion, Claude Code credential reader, token resolver.
  Handles system prompt extraction, tool_use/tool_result blocks,
  thinking/reasoning, orphaned tool_use cleanup, cache_control.
- tests/test_anthropic_adapter.py — 36 tests covering all adapter logic

### Modified files
- pyproject.toml — Add anthropic>=0.39.0 dependency
- hermes_cli/auth.py — Add 'anthropic' to PROVIDER_REGISTRY with
  three env vars, plus 'claude'/'claude-code' aliases
- hermes_cli/models.py — Add model catalog, labels, aliases, provider order
- hermes_cli/main.py — Add 'anthropic' to --provider CLI choices
- hermes_cli/runtime_provider.py — Add Anthropic branch returning
  api_mode='anthropic_messages' (before generic api_key fallthrough)
- hermes_cli/setup.py — Add Anthropic setup wizard with Claude Code
  credential auto-discovery, model selection, OpenRouter tools prompt
- agent/auxiliary_client.py — Add claude-haiku-4-5 as aux model
- agent/model_metadata.py — Add bare Claude model context lengths
- run_agent.py — Add anthropic_messages api_mode:
  * Client init (Anthropic SDK instead of OpenAI)
  * API call dispatch (_anthropic_client.messages.create)
  * Response validation (content blocks)
  * finish_reason mapping (stop_reason -> finish_reason)
  * Token usage (input_tokens/output_tokens)
  * Response normalization (normalize_anthropic_response)
  * Client interrupt/rebuild
  * Prompt caching auto-enabled for native Anthropic
- tests/test_run_agent.py — Update test_anthropic_base_url_accepted to
  expect native routing, add test_prompt_caching_native_anthropic
After studying clawdbot (OpenClaw) and OpenCode implementations:

## Beta headers
- Add interleaved-thinking-2025-05-14 and fine-grained-tool-streaming-2025-05-14
  as common betas (sent with ALL auth types, not just OAuth)
- OAuth tokens additionally get oauth-2025-04-20
- API keys now also get the common betas (previously got none)

## Vision/image support
- Add _convert_vision_content() to convert OpenAI multimodal format
  (image_url blocks) to Anthropic format (image blocks with base64/url source)
- Handles both data: URIs (base64) and regular URLs

## Role alternation enforcement
- Anthropic strictly rejects consecutive same-role messages (400 error)
- Add post-processing step that merges consecutive user/assistant messages
- Handles string, list, and mixed content types during merge

## Tool choice support
- Add tool_choice parameter to build_anthropic_kwargs()
- Maps OpenAI values: auto→auto, required→any, none→omit, name→tool

## Cache metrics tracking
- Anthropic uses cache_read_input_tokens / cache_creation_input_tokens
  (different from OpenRouter's prompt_tokens_details.cached_tokens)
- Add api_mode-aware branch in run_agent.py cache stats logging

## Credential refresh on 401
- On 401 error during anthropic_messages mode, re-read credentials
  via resolve_anthropic_token() (picks up refreshed Claude Code tokens)
- Rebuild client if new token differs from current one
- Follows same pattern as Codex/Nous 401 refresh handlers

## Tests
- 44 adapter tests (8 new: vision conversion, role alternation, tool choice)
- Updated beta header tests to verify new structure
- Full suite: 3198 passed, 0 regressions
…ion aux

Feedback fixes:

1. Revert _convert_vision_content — vision is handled by the vision_analyze
   tool, not by converting image blocks inline in conversation messages.
   Removed the function and its tests.

2. Add Anthropic to 'hermes model' (cmd_model in main.py):
   - Added to provider_labels dict
   - Added to providers selection list
   - Added _model_flow_anthropic() with Claude Code credential auto-detection,
     API key prompting, and model selection from catalog.

3. Wire up Anthropic as a vision-capable auxiliary provider:
   - Added _try_anthropic() to auxiliary_client.py using claude-sonnet-4
     as the vision model (Claude natively supports multimodal)
   - Added to the get_vision_auxiliary_client() auto-detection chain
     (after OpenRouter/Nous, before Codex/custom)

Cache tracking note: the Anthropic cache metrics branch in run_agent.py
(cache_read_input_tokens / cache_creation_input_tokens) is in the correct
place — it's response-level parsing, same location as the existing
OpenRouter cache tracking. auxiliary_client.py has no cache tracking.
Both 'hermes model' and 'hermes setup model' now present a clear
two-option auth flow when no credentials are found:

  1. Claude Pro/Max subscription (setup-token)
     - Step-by-step instructions to run 'claude setup-token'
     - User pastes the resulting sk-ant-oat01-... token

  2. Anthropic API key (pay-per-token)
     - Link to console.anthropic.com/settings/keys
     - User pastes sk-ant-api03-... key

Also handles:
  - Auto-detection of existing Claude Code creds (~/.claude/.credentials.json)
  - Existing credentials shown with option to update
  - Consistent UX between 'hermes model' and 'hermes setup model'
…2.x)

The critical bug: read_claude_code_credentials() only looked at
~/.claude/.credentials.json, but Claude Code's native binary (v2.x,
Bun-compiled) stores credentials in ~/.claude.json at the top level
as 'primaryApiKey'. The .credentials.json file is only written by
older npm-based installs.

Now checks both locations in priority order:
  1. ~/.claude.json → primaryApiKey (native binary, v2.x)
  2. ~/.claude/.credentials.json → claudeAiOauth.accessToken (legacy)

Verified live: hermes model → Anthropic → auto-detected credentials →
claude-sonnet-4-20250514 → 'Hello there, how are you?' (5 words)
- Add _fetch_anthropic_models() to hermes_cli/models.py — hits the
  Anthropic /v1/models endpoint to get the live model catalog. Handles
  both API key and OAuth token auth headers.

- Wire it into provider_model_ids() so both 'hermes model' and
  'hermes setup model' show the live list instead of a stale static one.

- Update static _PROVIDER_MODELS fallback with full current catalog:
  opus-4-6, sonnet-4-6, opus-4-5, sonnet-4-5, opus-4, sonnet-4, haiku-4-5

- Update model_metadata.py with context lengths for all current models.

- Fix thinking parameter for 4.5+ models: use type='adaptive' instead
  of type='enabled' (Anthropic deprecated 'enabled' for newer models,
  warns at runtime). Detects model version from the model name string.

Verified live:
  hermes model → Anthropic → auto-detected creds → shows 7 live models
  hermes chat --provider anthropic --model claude-opus-4-6 → works
Fixes from comprehensive code review and cross-referencing with
clawdbot/OpenCode implementations:

CRITICAL:
- Add one-shot guard (anthropic_auth_retry_attempted) to prevent
  infinite 401 retry loops when credentials keep changing
- Fix _is_oauth_token(): managed keys from ~/.claude.json are NOT
  regular API keys (don't start with sk-ant-api). Inverted the logic:
  only sk-ant-api* is treated as API key auth, everything else uses
  Bearer auth + oauth beta headers

HIGH:
- Wrap json.loads(args) in try/except in message conversion — malformed
  tool_call arguments no longer crash the entire conversation
- Raise AuthError in runtime_provider when no Anthropic token found
  (was silently passing empty string, causing confusing API errors)
- Remove broken _try_anthropic() from auxiliary vision chain — the
  centralized router creates an OpenAI client for api_key providers
  which doesn't work with Anthropic's Messages API

MEDIUM:
- Handle empty assistant message content — Anthropic rejects empty
  content blocks, now inserts '(empty)' placeholder
- Fix setup.py existing_key logic — set to 'KEEP' sentinel instead
  of None to prevent falling through to the auth choice prompt
- Add debug logging to _fetch_anthropic_models on failure

Tests: 43 adapter tests (2 new for token detection), 3197 total passed
…mp=1

Remaining issues from deep scan:

Adapter (agent/anthropic_adapter.py):
- Add _sanitize_tool_id() — Anthropic requires IDs matching [a-zA-Z0-9_-],
  now strips invalid chars and ensures non-empty (both tool_use and tool_result)
- Empty tool result content → '(no output)' placeholder (Anthropic rejects empty)
- Set temperature=1 when thinking type='enabled' on older models (required)
- normalize_model_name now case-insensitive for 'Anthropic/' prefix
- Fix stale docstrings referencing only ~/.claude/.credentials.json

Agent loop (run_agent.py):
- Guard memory flush path (line ~2684) — was calling self.client.chat.completions
  which is None in anthropic_messages mode. Now routes through Anthropic client.
- Guard summary generation path (line ~3171) — same crash when reaching
  iteration limit. Now builds proper Anthropic kwargs and normalizes response.
- Guard retry summary path (line ~3200) — same fix for the summary retry loop.

All three self.client.chat.completions.create() calls outside the main
loop now have anthropic_messages branches to prevent NoneType crashes.
- quickstart.md: Add Anthropic to the provider comparison table
- configuration.md: Add Anthropic to provider list table, add full
  'Anthropic (Native)' section with three auth methods (API key,
  setup-token, Claude Code auto-detect), config.yaml example,
  and provider alias tip
- environment-variables.md: Add ANTHROPIC_API_KEY, ANTHROPIC_TOKEN,
  CLAUDE_CODE_OAUTH_TOKEN to LLM Providers table; add 'anthropic'
  to HERMES_INFERENCE_PROVIDER values list
…c response format

The memory flush path extracted tool_calls from the response assuming
OpenAI format (response.choices[0].message.tool_calls). When using
the Anthropic client directly (aux unavailable), the response is an
Anthropic Message object which has no .choices attribute. Now uses
normalize_anthropic_response() to extract tool_calls correctly.
@teknium1
teknium1 merged commit 0219abf into main Mar 13, 2026
1 check failed
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 27, 2026
…c877bdeb

feat: native Anthropic provider with Claude Code credential auto-discovery
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…c877bdeb

feat: native Anthropic provider with Claude Code credential auto-discovery
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…c877bdeb

feat: native Anthropic provider with Claude Code credential auto-discovery
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant