mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-09 13:21:42 +00:00
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>
127 lines
4.1 KiB
Python
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
|