hermes-agent/tests/hermes_cli/test_custom_provider_extra_headers.py
Jneeee b98baa3039 feat(config): extra HTTP headers for LLM API calls (#3526 salvage)
Named providers / custom_providers entries in config.yaml now accept an
extra_headers dict scoped to that endpoint — for reverse proxies, API
gateways, and custom auth schemes (e.g. Cloudflare Access service tokens).

- hermes_cli/config.py: normalize extra_headers on provider entries
  (_normalize_custom_provider_entry + providers-dict translation), add
  get_custom_provider_extra_headers /
  apply_custom_provider_extra_headers_to_client_kwargs helpers keyed on
  base_url (case/trailing-slash insensitive, no substring bypass —
  mirrors the TLS helpers)
- hermes_cli/runtime_provider.py: surface extra_headers in the resolved
  runtime for named custom providers (providers dict, legacy
  custom_providers list, and the credential-pool path)
- run_agent.py / agent/agent_init.py: merge per-provider extra_headers
  onto the OpenAI client default_headers at construction and on every
  _apply_client_headers_for_base_url re-application (credential swaps,
  rebuilds), most-specific level wins; OpenAI-wire only (native
  Anthropic/Bedrock scoped out)
- agent/auxiliary_client.py: accept model.extra_headers as an alias of
  model.default_headers for the global variant
- cli-config.yaml.example: documented commented example
- Header values are treated as secrets and never logged

Salvaged from PR #3526 by @jneeee, reimplemented against current main.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-07-02 05:33:25 -07:00

127 lines
4.1 KiB
Python

"""Tests for per-provider ``extra_headers`` in providers / custom_providers config.
PR #3526 salvage — user-configurable extra HTTP headers on LLM API calls
(reverse proxies, gateways, custom auth such as Cloudflare Access tokens).
"""
from hermes_cli.config import (
_normalize_custom_provider_entry,
apply_custom_provider_extra_headers_to_client_kwargs,
get_custom_provider_extra_headers,
)
def test_normalize_entry_keeps_extra_headers():
normalized = _normalize_custom_provider_entry(
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": {"X-Custom-Auth": "tok", "X-Client-Name": "hermes"},
}
)
assert normalized is not None
assert normalized["extra_headers"] == {
"X-Custom-Auth": "tok",
"X-Client-Name": "hermes",
}
def test_normalize_entry_drops_invalid_extra_headers():
for bad in ("not-a-dict", {}, 42, ["a"]):
normalized = _normalize_custom_provider_entry(
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": bad,
}
)
assert normalized is not None
assert "extra_headers" not in normalized
def test_normalize_entry_stringifies_values_and_skips_none():
normalized = _normalize_custom_provider_entry(
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": {"X-Int": 7, "X-None": None},
}
)
assert normalized is not None
assert normalized["extra_headers"] == {"X-Int": "7"}
def test_get_custom_provider_extra_headers_matches_base_url():
providers = [
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": {"CF-Access-Client-Id": "xxxx.access"},
}
]
# trailing-slash and case insensitive match, mirroring the TLS helper
headers = get_custom_provider_extra_headers(
"https://LLM.internal.example.com/v1/",
custom_providers=providers,
)
assert headers == {"CF-Access-Client-Id": "xxxx.access"}
def test_get_custom_provider_extra_headers_no_match_returns_empty():
providers = [
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": {"X-Secret": "s"},
}
]
assert get_custom_provider_extra_headers(
"https://other.example.com/v1", custom_providers=providers,
) == {}
# prefix look-alike host must not match (no substring bypass)
assert get_custom_provider_extra_headers(
"https://llm.internal.example.com.attacker.test/v1",
custom_providers=providers,
) == {}
def test_apply_extra_headers_merges_onto_existing_defaults():
client_kwargs = {
"api_key": "x",
"base_url": "https://llm.internal.example.com/v1",
"default_headers": {"User-Agent": "curl/8.7.1", "X-Keep": "1"},
}
providers = [
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": {"User-Agent": "override", "X-New": "2"},
}
]
apply_custom_provider_extra_headers_to_client_kwargs(
client_kwargs,
"https://llm.internal.example.com/v1",
custom_providers=providers,
)
assert client_kwargs["default_headers"] == {
"User-Agent": "override", # provider-specific value wins
"X-Keep": "1", # untouched defaults preserved
"X-New": "2",
}
def test_apply_extra_headers_noop_without_match():
client_kwargs = {"api_key": "x", "base_url": "https://other.example.com/v1"}
providers = [
{
"name": "my-proxy",
"base_url": "https://llm.internal.example.com/v1",
"extra_headers": {"X-Secret": "s"},
}
]
apply_custom_provider_extra_headers_to_client_kwargs(
client_kwargs,
"https://other.example.com/v1",
custom_providers=providers,
)
assert "default_headers" not in client_kwargs