fix(aux-client): honor api_mode: anthropic_messages for named custom providers#15059
Merged
Merged
Conversation
…providers
Auxiliary tasks (session_search, flush_memories, approvals, compression,
vision, etc.) that route to a named custom provider declared under
config.yaml 'providers:' with 'api_mode: anthropic_messages' were
silently building a plain OpenAI client and POSTing to
{base_url}/chat/completions, which returns 404 on Anthropic-compatible
gateways that only expose /v1/messages.
Two gaps caused this:
1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the
providers-dict branch (new-style) returned only name/base_url/api_key/
model and dropped api_mode. The legacy custom_providers-list branch
already propagated it correctly. The dict branch now parses and
returns api_mode via _parse_api_mode() in both match paths.
2. agent/auxiliary_client.py::resolve_provider_client — the named
custom provider block at ~L1740 ignored custom_entry['api_mode']
and unconditionally built an OpenAI client (only wrapping for
Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way
dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped
in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient,
otherwise plain OpenAI. An explicit task-level api_mode override
still wins over the provider entry's declared api_mode.
Fixes #15033
Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a
TestProvidersDictApiModeAnthropicMessages class covering
- providers-dict preserves valid api_mode
- invalid api_mode values are dropped
- missing api_mode leaves the entry unchanged (no regression)
- resolve_provider_client returns (Async)AnthropicAuxiliaryClient for
api_mode=anthropic_messages
- full chain via get_text_auxiliary_client / get_async_text_auxiliary_client
with an auxiliary.<task> override
- providers without api_mode still use the OpenAI-wire path
nekorytaylor666
pushed a commit
to nekorytaylor666/hermes-agent
that referenced
this pull request
Apr 24, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
justrhoto
pushed a commit
to justrhoto/hermes-agent
that referenced
this pull request
Apr 24, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
4 tasks
This was referenced Apr 27, 2026
briandevans
added a commit
to briandevans/hermes-agent
that referenced
this pull request
Apr 28, 2026
…nv in task config (NousResearch#16254) Two sibling bugs in agent/auxiliary_client.py left the explicit_base_url code path (path 3) broken for non-OpenAI API formats. **Bug 1 — path 3 ignores api_mode: anthropic_messages** resolve_provider_client() has three independent branches that all do the same thing: given a (base_url, api_key, api_mode), construct the right SDK client. - Path 1 (_try_custom_endpoint, ~L1177): fixed by NousResearch#7648 - Path 2 (named custom providers, ~L1895): fixed by NousResearch#15059 - Path 3 (explicit_base_url branch, ~L1850): still broken — always built a plain OpenAI client and never checked api_mode Result: when auxiliary.<task> config has base_url + api_mode: anthropic_messages, the code sends an Anthropic-format body to <base_url>/chat/completions (the OpenAI endpoint) instead of /v1/messages, producing 404 invalid_model or 403. Fix: add the same api_mode == "anthropic_messages" dispatch that path 2 has, mirroring lines 1895-1914 exactly. Updated the docstring to remove the hardcoded "OpenAI-compatible" assumption. **Bug 2 — key_env silently dropped from auxiliary task configs** _resolve_task_provider_model() parsed api_key from task_config but had no equivalent for key_env. Contrast with _get_named_custom_provider() (path 2) which correctly resolves key_env via os.getenv(). Result: auxiliary.<task> configs that specify key_env instead of api_key always send api_key="no-key-required", producing 403 access_denied even when the env var is set. Fix: add the same two-line key_env resolution that path 2 uses. Before: test_explicit_base_url_anthropic_messages_returns_anthropic_client asserts isinstance(client, AnthropicAuxiliaryClient) → FAIL (got OpenAI) After: 7 new tests all pass; existing 95 auxiliary client tests unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ulasbilgen
pushed a commit
to ulasbilgen/hermes-adhd-agent
that referenced
this pull request
May 1, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
aj-nt
pushed a commit
to aj-nt/hermes-agent
that referenced
this pull request
May 1, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
donald131
pushed a commit
to donald131/hermes-agent
that referenced
this pull request
May 2, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
02356abc
pushed a commit
to 02356abc/hermes-agent
that referenced
this pull request
May 14, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
gweeteve
pushed a commit
to gweeteve/hermes-agent
that referenced
this pull request
Jun 2, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
Egavasyug
pushed a commit
to Egavasyug/hermes-agent
that referenced
this pull request
Jun 10, 2026
…providers (NousResearch#15059) Auxiliary tasks (session_search, flush_memories, approvals, compression, vision, etc.) that route to a named custom provider declared under config.yaml 'providers:' with 'api_mode: anthropic_messages' were silently building a plain OpenAI client and POSTing to {base_url}/chat/completions, which returns 404 on Anthropic-compatible gateways that only expose /v1/messages. Two gaps caused this: 1. hermes_cli/runtime_provider.py::_get_named_custom_provider — the providers-dict branch (new-style) returned only name/base_url/api_key/ model and dropped api_mode. The legacy custom_providers-list branch already propagated it correctly. The dict branch now parses and returns api_mode via _parse_api_mode() in both match paths. 2. agent/auxiliary_client.py::resolve_provider_client — the named custom provider block at ~L1740 ignored custom_entry['api_mode'] and unconditionally built an OpenAI client (only wrapping for Codex/Responses). It now mirrors _try_custom_endpoint()'s three-way dispatch: anthropic_messages → AnthropicAuxiliaryClient (async wrapped in AsyncAnthropicAuxiliaryClient), codex_responses → CodexAuxiliaryClient, otherwise plain OpenAI. An explicit task-level api_mode override still wins over the provider entry's declared api_mode. Fixes NousResearch#15033 Tests: tests/agent/test_auxiliary_named_custom_providers.py gains a TestProvidersDictApiModeAnthropicMessages class covering - providers-dict preserves valid api_mode - invalid api_mode values are dropped - missing api_mode leaves the entry unchanged (no regression) - resolve_provider_client returns (Async)AnthropicAuxiliaryClient for api_mode=anthropic_messages - full chain via get_text_auxiliary_client / get_async_text_auxiliary_client with an auxiliary.<task> override - providers without api_mode still use the OpenAI-wire path
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Auxiliary tasks that use a named custom provider with
api_mode: anthropic_messagesnow correctly route through the Anthropic Messages API instead of 404'ing against/chat/completions.Root cause
Two gaps in the same flow:
_get_named_custom_provider(hermes_cli/runtime_provider.py) — the new-styleproviders:dict branch dropped theapi_modefield when materializing the provider entry. The legacycustom_providers:list branch already propagated it.resolve_provider_client(agent/auxiliary_client.py, named-custom block ~L1740) — ignoredcustom_entry["api_mode"]entirely and built a plainOpenAIclient every time, only wrapping for Codex.Changes
hermes_cli/runtime_provider.py: both match paths in the providers-dict branch now parse and attachapi_modevia_parse_api_mode().agent/auxiliary_client.py: the named-custom branch now mirrors_try_custom_endpoint()'s three-way dispatch:anthropic_messages→AnthropicAuxiliaryClient(async:AsyncAnthropicAuxiliaryClient)codex_responses→CodexAuxiliaryClientTask-level
api_modeoverride still wins over the provider entry's declared mode.TestProvidersDictApiModeAnthropicMessagesclass (6 cases) covers propagation, invalid-mode drop, no-mode passthrough, both sync and async client types, and the fullauxiliary.<task>chain.Validation
tests/agent/test_auxiliary_named_custom_providers.pyE2E: reproduced the exact config from the issue (Anthropic-compatible gateway +
auxiliary.flush_memories.provider: myrelay);get_async_text_auxiliary_client("flush_memories")now returnsAsyncAnthropicAuxiliaryClientwrapping the Anthropic SDK, pointing at{base_url}/v1/messages.Closes #15033