Skip to content

fix(agent): honor model.default_headers for custom OpenAI-compatible providers (#40033)#40403

Closed
sanidhyasin wants to merge 1 commit into
NousResearch:mainfrom
sanidhyasin:fix/custom-provider-default-headers
Closed

fix(agent): honor model.default_headers for custom OpenAI-compatible providers (#40033)#40403
sanidhyasin wants to merge 1 commit into
NousResearch:mainfrom
sanidhyasin:fix/custom-provider-default-headers

Conversation

@sanidhyasin

Copy link
Copy Markdown
Contributor

Summary

Fixes #40033 — a custom OpenAI-compatible provider can fail with an opaque HTTP 502: Upstream access forbidden (or a 4xx) even though the identical API key, base URL, model, and JSON body succeed with curl. The reporter traced it to the OpenAI Python SDK's default identifying headers — User-Agent: OpenAI/Python ... and the X-Stainless-* family — being rejected by an upstream gateway/WAF in front of the OpenAI-compatible endpoint.

There was no supported way to override those headers, so the only fix was to patch the local install.

Root cause

For OpenAI-wire providers, default_headers is set from a fixed set of base-URL/provider rules (agent/agent_init.py at construction; AIAgent._apply_client_headers_for_base_url on credential swaps / client rebuilds). A generic custom endpoint matches none of those rules, so it inherits the SDK's built-in identifying headers with no user-facing override.

Fix

Add a model.default_headers config key. Its entries are merged onto the OpenAI client's default_headers with user values taking precedence over provider- and SDK-supplied defaults, so a custom endpoint can swap in a plain User-Agent (or override X-Stainless-*) and reach the upstream.

  • New AIAgent._apply_user_default_headers() reads model.default_headers via the existing cfg_get / load_config helpers and merges it on top of whatever headers were already resolved.
  • Called both at initial client construction (agent/agent_init.py) and at the end of _apply_client_headers_for_base_url, so the override survives credential rotation and client rebuilds — not just the first request.
  • No-op for native Anthropic / Bedrock modes (they don't use the OpenAI client) and when the key is unset, so existing behaviour and the provider/attribution defaults are unchanged.

Config (now works)

model:
  provider: custom
  default: <model>
  base_url: http://<host>/v1
  default_headers:
    User-Agent: "curl/8.7.1"

This matches suggested fix option 1 in the issue. Documented in cli-config.yaml.example under the model: section.

Tests

tests/run_agent/test_provider_attribution_headers.py — 4 new tests:

Full file passes (13 tests). Ran with the project venv on macOS (Python 3.11). ruff check clean on the changed files.

Platforms

Logic is platform-independent (header dict merge + config read). Tested on macOS; no OS-specific code paths touched.

…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.
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent loop, run_agent.py, prompt builder P3 Low — cosmetic, nice to have provider/openrouter OpenRouter aggregator labels Jun 6, 2026
kshitijk4poor added a commit to kshitijk4poor/hermes-agent that referenced this pull request Jun 7, 2026
Adds the AUTHOR_MAP entry for the NousResearch#40403 salvage (model.default_headers
for custom OpenAI-compatible providers, fixes NousResearch#40033) so contributor_audit
passes when the salvage PR lands.
kshitijk4poor added a commit that referenced this pull request Jun 7, 2026
Adds the AUTHOR_MAP entry for the #40403 salvage (model.default_headers
for custom OpenAI-compatible providers, fixes #40033) so contributor_audit
passes when the salvage PR lands.
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #41096 — your commit was cherry-picked with authorship preserved (it's on main as a216ff839). The salvage added a follow-up commit extending the same model.default_headers override to the auxiliary client (title/compression/vision calls build their own OpenAI clients, so a custom WAF'd endpoint needed the override on both paths). Thanks for the clean fix and the thorough issue analysis! 🙏

@sanidhyasin

Copy link
Copy Markdown
Contributor Author

Thanks for the salvage and for preserving authorship — appreciated. The auxiliary-client follow-up is the right call; I'd scoped only the main client and missed that title/compression/vision build their own OpenAI clients, so the override would've silently applied to the main turn but not aux calls on the same WAF'd endpoint. Good catch. 🙏

changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
…search#41094)

Adds the AUTHOR_MAP entry for the NousResearch#40403 salvage (model.default_headers
for custom OpenAI-compatible providers, fixes NousResearch#40033) so contributor_audit
passes when the salvage PR lands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P3 Low — cosmetic, nice to have provider/openrouter OpenRouter aggregator type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom OpenAI-compatible provider can fail when upstream blocks OpenAI Python SDK default headers

3 participants