fix: honor model.default_headers for custom OpenAI-compatible providers, main + auxiliary clients (#40033)#41096
Conversation
5e5c11c to
0dca2bd
Compare
|
Positive verification — reviewed the full diff and test suite. The implementation is clean and consistent:
One minor observation (non-blocking): No issues found — this is ready to merge. |
0dca2bd to
cb0feda
Compare
…providers (NousResearch#40033) Custom OpenAI-compatible endpoints sitting behind a gateway/WAF can reject the OpenAI Python SDK's default identifying headers (User-Agent: OpenAI/Python, X-Stainless-*) and return an opaque 502/4xx even though the same request body succeeds under curl. There was no supported way to override those headers. Add a model.default_headers config key whose values are merged onto the OpenAI client's default_headers, taking precedence over provider- and SDK-supplied defaults. Applied at client construction and on every credential swap / client rebuild so the override survives reconnects. No-op for native Anthropic / Bedrock modes and when unconfigured.
…search#40033) The salvaged main-agent fix (sanidhyasin) applies model.default_headers to the primary OpenAI client, but the auxiliary client (title generation, context compression, vision routing) builds its own clients and did not read the override. For a `provider: custom` endpoint behind a gateway/WAF that rejects the OpenAI SDK's identifying headers, the main turn would succeed while auxiliary calls to the same endpoint still failed with the opaque 502/4xx from NousResearch#40033. Add agent.auxiliary_client._apply_user_default_headers() (user values win over provider/SDK defaults; no-op when unconfigured) and apply it at every OpenAI-wire client construction site: - _try_custom_endpoint() — config-level `model.provider: custom` - the named custom-provider branch (custom_providers/providers entries), including the anthropic-SDK-missing OpenAI-wire fallback - the api-key-provider, async-conversion, and main resolve_provider_client fallback branches To prevent the two clients ever drifting on precedence/value handling, AIAgent._apply_user_default_headers (run_agent.py) now delegates the config read + merge to this shared helper (run_agent already imports from auxiliary_client). Native Anthropic/Bedrock branches are untouched (they don't use the OpenAI wire). 8 new tests (helper semantics + config-level custom + named custom); full aux + attribution header suites green (295).
cb0feda to
fcf21f0
Compare
Summary
Salvages #40403 (sanidhyasin) onto current
mainand completes the fix across both client paths, fixing #40033.A
customOpenAI-compatible provider can fail with an opaqueHTTP 502: Upstream access forbidden(or a 4xx) even though the identical API key, base URL, model, and JSON body succeed withcurl. The cause is an upstream gateway/WAF rejecting the OpenAI Python SDK's identifying headers (User-Agent: OpenAI/Python ...,X-Stainless-*). There was no supported way to override them.This adds a
model.default_headersconfig key whose entries are merged onto OpenAI-wire clients with user values taking precedence over provider/SDK defaults, so a custom endpoint can swap in a plainUser-Agent.Why two commits
fix(agent): honor model.default_headers …(sanidhyasin, cherry-picked, authorship preserved) — applies the override to the main agent OpenAI client at construction (agent/agent_init.py) and across credential swaps / client rebuilds (run_agent.py::_apply_client_headers_for_base_url). No-op for native Anthropic/Bedrock.fix(aux): honor model.default_headers on auxiliary client too(follow-up) — the original fix only covered the main client. The auxiliary client (title generation, context compression, vision routing) builds its own OpenAI clients, so for acustomWAF'd endpoint the main turn would succeed while auxiliary calls to the same endpoint still failed. This adds a sharedagent.auxiliary_client._apply_user_default_headers()and applies it at every OpenAI-wire construction site — config-levelmodel.provider: custom(_try_custom_endpoint), named custom providers (custom_providers/providersentries, incl. the anthropic-SDK-missing OpenAI-wire fallback), the api-key-provider, async-conversion, and main resolve fallback branches. To prevent the two clients ever drifting on precedence/value handling, the main-path method now delegates the config read + merge to this single shared helper.E2E verified
With a real isolated
HERMES_HOME+config.yaml, the override reaches the OpenAI client on all three paths:provider: customauxiliary client ✓custom_providersauxiliary client ✓Tests
tests/run_agent/test_provider_attribution_headers.py— 4 tests from fix(agent): honor model.default_headers for custom OpenAI-compatible providers (#40033) #40403 (main path): custom override, user-wins-over-provider-defaults, unconfigured no-op, Anthropic skip.tests/agent/test_auxiliary_user_default_headers.py— 8 new tests: helper merge semantics (user-wins, no-op, None-handling), config-level custom aux, named-custom aux.ruffclean.Design notes
model.default_headersis a documented global escape hatch. A reviewer flagged this; it's a low-risk, by-design tradeoff, not a footgun in practice (one wouldn't set a globalcurlUA while routing aux to kimi.com).${ENV_VAR}interpolation in header values is intentionally out of scope (separate concern, one-PR-one-concern) — can be a follow-up.Credit
model.default_headersconfig key + correct precedence + main-path implementation + docs + main-path tests.Original issue #40033 reported by @danfye.
Closes #40403
Closes #40033