Skip to content

fix(auxiliary): pass reasoning_config and extra_body through to auxiliary Anthropic calls - #37217

Closed
dorokuma wants to merge 1 commit into
NousResearch:mainfrom
dorokuma:fix/auxiliary-anthropic-passthrough
Closed

fix(auxiliary): pass reasoning_config and extra_body through to auxiliary Anthropic calls#37217
dorokuma wants to merge 1 commit into
NousResearch:mainfrom
dorokuma:fix/auxiliary-anthropic-passthrough

Conversation

@dorokuma

@dorokuma dorokuma commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

_AnthropicCompletionsAdapter.create() in agent/auxiliary_client.py silently discards caller-supplied reasoning_config and extra_body for every auxiliary task routed through the Anthropic-Messages protocol (anthropic, minimax, kimi-coding, z.ai, any custom /anthropic-suffixed endpoint). The main agent path (agent/transports/anthropic.py) already handles these correctly; this PR aligns the auxiliary adapter with the same pattern.

Two bugs, one root cause

Bug A — reasoning_config hardcoded to None (L1000)

anthropic_kwargs = build_anthropic_kwargs(
    ...
    reasoning_config=None,   # always None, regardless of caller
    ...
)

The main agent path already does this right:

return build_anthropic_kwargs(..., reasoning_config=params.get("reasoning_config"), ...)

Bug B — extra_body dropped between caller and SDK (L1012)

create(**kwargs) accepts the caller's OpenAI-style kwargs but only forwards a hand-picked subset to self._client.messages.create(**anthropic_kwargs). extra_body (and any other caller-supplied field) never reaches the wire. The codex/responses transport in the same file already merges extra_body; the Anthropic branch is the gap.

Fix

--- a/agent/auxiliary_client.py
+++ b/agent/auxiliary_client.py
@@ -997,7 +997,7 @@ class _AnthropicCompletionsAdapter:
             messages=messages,
             tools=tools,
             max_tokens=max_tokens,
-            reasoning_config=None,
+            reasoning_config=kwargs.get("reasoning_config"),
             tool_choice=normalized_tool_choice,
             is_oauth=self._is_oauth,
         )
@@ -1009,6 +1009,19 @@ class _AnthropicCompletionsAdapter:
             if not _forbids_sampling_params(model):
                 anthropic_kwargs["temperature"] = temperature

+        # Merge caller-supplied extra_body so providers behind
+        # Anthropic-compatible gateways can receive per-vendor request
+        # fields (e.g. thinking control, metadata, service_tier). Dict
+        # form is the documented Anthropic SDK passthrough for
+        # non-standard request body keys.
+        caller_extra_body = kwargs.get("extra_body")
+        if caller_extra_body and isinstance(caller_extra_body, dict):
+            existing = anthropic_kwargs.get("extra_body") or {}
+            if not isinstance(existing, dict):
+                existing = {}
+            anthropic_kwargs["extra_body"] = {**existing, **caller_extra_body}
+
         response = self._client.messages.create(**anthropic_kwargs)

Behavior

  • Backward-compatible. Callers that don't pass reasoning_config or extra_body see no change.
  • Unlocks caller extra_body for the auxiliary path. Callers can now pass extra_body={"thinking": {"type": "disabled"}} (or any vendor field) via the standard OpenAI-style kwarg and have it reach the Anthropic SDK.
  • reasoning_config kwarg now flows into build_anthropic_kwargs like the main agent does, instead of being silently replaced with None.

Tests

Add a unit test under tests/agent/ mocking self._client.messages.create() and asserting:

  1. reasoning_config={"enabled": True, "effort": "medium"} from the caller → build_anthropic_kwargs is invoked with that value.
  2. extra_body={"thinking": {"type": "disabled"}} from the caller → kwargs to messages.create() include the merged dict under extra_body.
  3. Neither passed → kwargs to messages.create() are byte-identical to today (no regression).

Related issues

Same family of issue, different surfaces — flagging for reviewers:

…iary Anthropic calls

Two related bugs in _AnthropicCompletionsAdapter.create() in
agent/auxiliary_client.py silently discard caller-supplied
reasoning_config and extra_body on the Anthropic-Messages
auxiliary-protocol path:

  * Bug A: reasoning_config=None was hardcoded at L1000, so the
    reasoning_config parameter on build_anthropic_kwargs was
    unreachable for any auxiliary task. The main agent path
    (agent/transports/anthropic.py) already reads
    reasoning_config from caller params; this PR aligns the
    auxiliary adapter with the same pattern.

  * Bug B: create(**kwargs) accepts an OpenAI-style kwargs
    payload from the caller but only forwards a hand-picked
    subset to self._client.messages.create(). Any caller-supplied
    extra_body (e.g. thinking control, metadata, service_tier,
    vendor-specific fields) was dropped on the floor. The
    codex/responses transport in the same file already merges
    extra_body; the Anthropic branch is the gap.

This unlocks the caller-supplied extra_body path so auxiliary
callers can set per-vendor request fields (including
thinking: {type: "disabled"} for Anthropic-compatible vendors
that require an explicit disable on the wire), and lets the
reasoning_config kwarg flow into build_anthropic_kwargs like the
main agent does. Both changes are backward-compatible for
callers that don't pass the affected kwargs.

Affected providers (all routed through _AnthropicCompletionsAdapter
via _maybe_wrap_anthropic): anthropic (native), minimax /
minimax-cn, kimi-coding / kimi-coding-cn, z.ai / GLM, and any
custom /anthropic-suffixed endpoint. See PR description for
related issues (#35566, #7209, #16533, #32813, #29248).
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint labels Jun 2, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for identifying the auxiliary Anthropic parity gap. It still exists on current main: agent/auxiliary_client.py:1283 hardcodes reasoning_config=None, while the main transport forwards its caller value at agent/transports/anthropic.py:70.

Problems

  • The patch predates the current dispatch refactor. Current main calls create_anthropic_message() at agent/auxiliary_client.py:1295, rather than the direct SDK call in the PR diff. The helper forwards the constructed kwargs to messages.stream()/messages.create() at agent/anthropic_adapter.py:2773-2790, so the extra_body merge needs to be placed before that helper call.
  • No regression test is included. Existing adapter tests use a mocked create_anthropic_message() seam at tests/agent/test_auxiliary_client.py:1260-1276, which is suitable for verifying both passthroughs.

Suggested changes

  • Rebase the implementation concept onto the current helper-based adapter: forward kwargs.get("reasoning_config") at agent/auxiliary_client.py:1283, merge dict extra_body into anthropic_kwargs, then dispatch through the helper.
  • Add focused tests for forwarded reasoning configuration, forwarded/merged extra_body, and the no-extra-input baseline.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
teknium1 added a commit that referenced this pull request Jul 15, 2026
…sions)

Five tests for the salvaged #37217 Bug B fix: vendor-field passthrough,
reasoning-key + private-key exclusion, merge-over-existing (fast-mode
speed), no-extra_body regression guard, reasoning-only adds nothing.

Live probes against api.anthropic.com informed the exclusion design:
Anthropic strictly validates the request body (unknown keys 400 with
'Extra inputs are not permitted'), so the passthrough forwards only
caller-configured fields and never the OpenAI-shaped reasoning dict
(translated natively) or _-private plumbing keys.
@teknium1

Copy link
Copy Markdown
Contributor

Both halves of this PR are now on main — closing with full credit, @dorokuma. Your diagnosis was exactly right on both counts:

Bug A (reasoning_config hardcoded to None): fixed via #64597/#64631, which route reasoning through build_anthropic_kwargs with per-task and per-slot resolution.

Bug B (extra_body dropped between caller and SDK): your commit was cherry-picked onto current main with your authorship preserved (rebase merge, commit 771571a) via PR #64942.

One scoping change on top of your merge block, informed by live probes against api.anthropic.com: the Messages API strictly rejects unknown body keys (400 "Extra inputs are not permitted"), including the OpenAI-shaped reasoning dict — which Hermes' own per-task config now places in extra_body. The merged passthrough therefore excludes reasoning (it's translated natively into thinking) and _-prefixed private keys, and merges over adapter-emitted fields rather than clobbering. Everything else a caller configures reaches the wire exactly as you intended.

Thanks for the precise two-bug analysis — the PR body's line-level diagnosis made this salvage straightforward.

@teknium1 teknium1 closed this Jul 15, 2026
@dorokuma

Copy link
Copy Markdown
Contributor Author

Appreciate the thorough follow-up and the credit — glad the analysis helped make the fix straightforward.

Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…sions)

Five tests for the salvaged NousResearch#37217 Bug B fix: vendor-field passthrough,
reasoning-key + private-key exclusion, merge-over-existing (fast-mode
speed), no-extra_body regression guard, reasoning-only adds nothing.

Live probes against api.anthropic.com informed the exclusion design:
Anthropic strictly validates the request body (unknown keys 400 with
'Extra inputs are not permitted'), so the passthrough forwards only
caller-configured fields and never the OpenAI-shaped reasoning dict
(translated natively) or _-private plumbing keys.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…sions)

Five tests for the salvaged NousResearch#37217 Bug B fix: vendor-field passthrough,
reasoning-key + private-key exclusion, merge-over-existing (fast-mode
speed), no-extra_body regression guard, reasoning-only adds nothing.

Live probes against api.anthropic.com informed the exclusion design:
Anthropic strictly validates the request body (unknown keys 400 with
'Extra inputs are not permitted'), so the passthrough forwards only
caller-configured fields and never the OpenAI-shaped reasoning dict
(translated natively) or _-private plumbing keys.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
…sions)

Five tests for the salvaged NousResearch#37217 Bug B fix: vendor-field passthrough,
reasoning-key + private-key exclusion, merge-over-existing (fast-mode
speed), no-extra_body regression guard, reasoning-only adds nothing.

Live probes against api.anthropic.com informed the exclusion design:
Anthropic strictly validates the request body (unknown keys 400 with
'Extra inputs are not permitted'), so the passthrough forwards only
caller-configured fields and never the OpenAI-shaped reasoning dict
(translated natively) or _-private plumbing keys.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants