Allow provider configs to inject custom request-body fields

Open 💬 6 comments Opened Oct 21, 2025 by MarianoFacundoArch

What feature would you like to see?

Add a config option (e.g., model_providers.<id>.extra_body) that lets us merge arbitrary JSON into the POST body for both Responses and Chat providers so we can turn on vendor-specific capabilities such as Anthropic’s thinking block.

Additional information

Right now Codex always sends the same minimal payload, which prevents enabling Claude’s extended thinking (thinking: { type: "enabled", budget_tokens: N }). A per-provider payload hook would unlock that while keeping defaults unchanged, ideally with validation that merges cleanly and preserves the existing fields.

View original on GitHub ↗

6 Comments

MarianoFacundoArch · 10 months ago

[Codex analysis on how to add this capability].

  - Add a new optional field to ModelProviderInfo (likely named something like extra_body) and make sure it deserializes
    from both TOML config files and CLI --config flags. This touches codex-rs/core/src/model_provider_info.rs and the
    surrounding Serde definitions.
  - Update config documentation (docs/config.md) to describe the new field, with examples for both chat and responses
    providers.
  - Thread that field through to Config so it’s available when requests are built (codex-rs/core/src/config.rs and related
    config-loading logic).
  - Update the request builders:
      - For chat-completion providers, clone the existing payload (see codex-rs/core/src/chat_completions.rs:270+) into a
        mutable serde_json::Value and merge in extra_body before sending.
      - For responses providers, adjust ResponsesApiRequest/serialization in codex-rs/core/src/client_common.rs so the
        extra fields get merged safely.
  - Add validation so required base fields (model, messages, streaming flags, etc.) can’t be removed or overridden in a
    way that breaks Codex.
  - Write tests (unit or integration) to prove the new field is parsed correctly and incorporated into outbound requests
    —use the existing mock servers in codex-rs/core/tests/suite or codex-rs/app-server/tests to assert the JSON body now
    contains the extra block.
  - Update any tooling or snapshot tests affected by the change, run the usual just fmt/just fix -p/targeted cargo test to
    keep the repo lint- and test-clean.

  So the main code lives in codex-rs/core/src/model_provider_info.rs, codex-rs/core/src/config.rs, and the two request
  implementations (chat_completions.rs and client.rs). Documentation sits in docs/config.md, and you’ll probably want
  regression tests under codex-rs/core/tests.
amchii · 8 months ago

I previously raised an issue about extra_body: https://github.com/openai/codex/issues/4267, but it was closed by them.

SproutSeeds · 5 months ago

I did a local repro against NVIDIA-hosted NIM and put the artifacts here:

The important split is that #5458 still looks relevant, but it does not seem
to be the first blocking boundary on the tested hosted NVIDIA path.

Exact results:

  • Codex custom providers only support wire_api = "responses" today
  • direct GET https://integrate.api.nvidia.com/v1/models returned 200
  • direct POST https://integrate.api.nvidia.com/v1/responses returned

404 page not found

  • the same /v1/responses 404 also appeared in a widened six-model matrix on

the same hosted Integrate surface across NVIDIA, Meta, and Mistral instruct
models

  • codex exec through a custom provider configured for

https://integrate.api.nvidia.com/v1 failed on that same /v1/responses
boundary

  • manual POST /v1/chat/completions with

nvidia/nemotron-3-super-120b-a12b returned 200

  • NVIDIA's chat_template_kwargs.force_nonempty_content = true guidance

materially changed the chat response shape in the fallback test

NVIDIA's current LLM NIM release notes describe experimental Responses API
support, so this looks more like a real hosted compatibility/data point than a
local config typo.

The strongest current read I can support is:

  • hosted NVIDIA Integrate is usable through chat/completions
  • this tested hosted path is not currently usable for direct Codex custom

providers because /v1/responses returns 404

  • on the tested Integrate surface, that now looks broader than a one-model

quirk

  • if another hosted or self-hosted NIM path exposes working Responses

semantics later, #5458 still looks like the right place for the
provider-level request-body-tuning part

SproutSeeds · 5 months ago

I pushed the NVIDIA separation one step further and updated the repro repo:

The new part is a real self-hosted NIM run on local hardware, not just the hosted Integrate checks.

Exact results:

  • on umbra (RTX 4090, Windows/NVIDIA driver 595.79), a warmed self-hosted NIM deployment now returns:
  • GET /v1/health/ready -> 200
  • GET /v1/models -> 200
  • POST /v1/chat/completions -> 200
  • POST /v1/responses -> 404
  • the self-hosted route list advertises /v1/chat/completions, /v1/models, and /v1/health/ready, but not /v1/responses
  • the repro repo now includes the self-hosted proof doc and scripted artifact bundle

So the first blocker on the tested NVIDIA paths is no longer just "hosted Integrate returns 404 on /v1/responses". The same missing Responses boundary also shows up on the first self-hosted NIM route I was able to bring up.

That keeps #5458 relevant as a later primitive if a working NVIDIA Responses route appears and still needs provider-level request-body tuning, but it still does not look like the first blocking boundary on the tested NVIDIA routes.

SproutSeeds · 5 months ago

I pushed the stronger local workaround tranche here:

This still does not change the first blocking boundary on the tested
NVIDIA routes: native /v1/responses is still missing on both the hosted and
first self-hosted path I tested.

What is materially stronger now is the local workaround story.

Exact results:

  • a thin local shim can already expose /v1/responses by translating Codex

requests into NVIDIA chat/completions

  • real codex exec now succeeds end-to-end through that shim on the tested

self-hosted NIM route

  • the cold-start split is now packaged, not just measured:
  • already-warm patched minimal-profile run:
  • final OK
  • real 69.47
  • after a true remote restart, direct replay of the exact minimal-profile

request still times out at 120s

  • exact-request prewarm carries that same cold-start path:
  • first prewarm 200 in 187.466568s
  • timed second request 200 in 58.247264s
  • the same workaround is now a checked-in one-command path:
  • wrapper:

scripts/smoke-codex-shim-cold-start.sh

  • fixture:

configs/nim.minimal-profile.prewarm-request.json

  • strongest wrapper run:
  • prewarm 200 in 180.839636s
  • final OK
  • real 323.73
  • on the Codex side, a separate local patch removed the fallback custom-provider

metadata warning on configured providers; after that, the next exposed seams
were shim /v1/models compatibility details and self-hosted cold-start
latency, not the old metadata warning

So the current split I can support is:

  • native NVIDIA /v1/responses availability is still the first blocker for

direct Codex custom-provider use on the tested paths

  • but a shim plus exact-request prewarm is already enough to make the

self-hosted path work end-to-end today

  • if useful, I can split the provider-metadata fix itself into a separate

Codex-side patch from the NIM-specific workaround repo

oxysoft · 3 months ago

Concrete workaround/data point for the chat-only upstream case: we just published the local adapter we are using to run DeepSeek V4 in Codex today:

https://github.com/holo-q/deepseek-responses-proxy

It exposes a local Responses-compatible endpoint to Codex and forwards to an OpenAI-compatible /chat/completions upstream. Current working config on Codex 0.128.0:

[model_providers.deepseek]
name = "DeepSeek"
base_url = "http://127.0.0.1:8787/v1"
experimental_bearer_token = "codex-deepseek-local"
wire_api = "responses"

[profiles.deepseek-v4-pro]
model_provider = "deepseek"
model = "deepseek-v4-pro"
model_context_window = 1000000
approval_policy = "untrusted"
sandbox_mode = "workspace-write"

For DeepSeek V4 specifically, the adapter also preserves/replays reasoning_content in chat history when tool calls are involved, which DeepSeek rejects if lost between turns.

This does not remove the need for provider-level extra_body; that would still be the clean native primitive for provider-specific knobs. But for upstreams that only expose /chat/completions while Codex wants wire_api = "responses", a thin local Responses->Chat adapter is enough to make it usable today.