Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1552,9 +1552,14 @@ def create(self, **kwargs) -> Any:
# with a 400.
effort = reasoning_cfg.get("effort") or "medium"
# Codex backend rejects "minimal"; clamp to "low" to
# match the main-agent Codex transport behavior.
# match the main-agent Codex transport behavior. "ultra"
# is Hermes-internal ladder vocabulary with no wire
# equivalent anywhere on this API; cap it at "max"
# (same class as #89503).
if effort == "minimal":
effort = "low"
elif effort == "ultra":
effort = "max"
resp_kwargs["reasoning"] = {
"effort": effort,
"summary": "auto",
Expand Down
64 changes: 54 additions & 10 deletions agent/transports/chat_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,21 @@ def _add_prompt_cache_key(


def _reasoning_config_for_model(model: str, reasoning_config: dict | None) -> dict | None:
"""Return the model's wire-compatible reasoning config."""
"""Return the model's wire-compatible reasoning config.

Hermes' internal effort set extends the wire vocabulary with ``ultra``
(the /reasoning command documents none..xhigh|max|ultra). OpenAI-
compatible wires — OpenRouter chief among them — accept exactly
max|xhigh|high|medium|low|minimal|none and reject the extension with
HTTP 400, so an ``ultra`` configured for an Anthropic default leaks
untranslated when a per-job/per-turn override pins a non-Anthropic
model on this transport and the whole call fails (#89503). Map the
extension to its wire cap for every model on this path; the Anthropic
adapter keeps its own richer mapping.
"""
if not isinstance(reasoning_config, dict):
return reasoning_config
if (
"gpt-5.6" in (model or "").lower()
and str(reasoning_config.get("effort") or "").strip().lower() == "ultra"
):
if str(reasoning_config.get("effort") or "").strip().lower() == "ultra":
normalized = dict(reasoning_config)
normalized["effort"] = "max"
return normalized
Expand Down Expand Up @@ -559,11 +567,36 @@ def build_kwargs(
and reasoning_config.get("enabled") is False
)
if not _kimi_thinking_off:
_kimi_effort = "medium"
# K3 accepts low/high/max only (default high) — "medium" and
# Hermes' upper-ladder levels 400 or silently degrade. Mirror
# the kimi-coding plugin's _K3_EFFORT_MAP; older Kimi models
# keep the low/medium/high vocabulary with the stronger
# Hermes levels capped at high instead of being dropped
# (dropping them inverted the ladder: ultra sent the
# "medium" default, weaker than an explicit high).
_e = ""
if reasoning_config and isinstance(reasoning_config, dict):
_e = (reasoning_config.get("effort") or "").strip().lower()
if _e in {"low", "medium", "high"}:
_kimi_effort = _e
if "k3" in (model or "").lower():
_kimi_effort = {
"minimal": "low",
"low": "low",
"medium": "high",
"high": "high",
"xhigh": "max",
"max": "max",
"ultra": "max",
}.get(_e, "high")
else:
_kimi_effort = {
"minimal": "low",
"low": "low",
"medium": "medium",
"high": "high",
"xhigh": "high",
"max": "high",
"ultra": "high",
}.get(_e, "medium")
api_kwargs["reasoning_effort"] = _kimi_effort

# Tencent TokenHub: top-level reasoning_effort (unless thinking disabled)
Expand All @@ -574,11 +607,22 @@ def build_kwargs(
and reasoning_config.get("enabled") is False
)
if not _tokenhub_thinking_off:
# TokenHub accepts low/medium/high. Map Hermes' full ladder
# onto that set instead of dropping unknown levels to the
# "high" default — dropping inverted the ladder for
# "minimal" (asked for the least, got the most).
_tokenhub_effort = "high"
if reasoning_config and isinstance(reasoning_config, dict):
_e = (reasoning_config.get("effort") or "").strip().lower()
if _e in {"low", "medium", "high"}:
_tokenhub_effort = _e
_tokenhub_effort = {
"minimal": "low",
"low": "low",
"medium": "medium",
"high": "high",
"xhigh": "high",
"max": "high",
"ultra": "high",
}.get(_e, "high")
api_kwargs["reasoning_effort"] = _tokenhub_effort

# LM Studio: top-level reasoning_effort. Only emit when the model
Expand Down
12 changes: 8 additions & 4 deletions agent/transports/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,14 @@ def build_kwargs(
elif reasoning_config.get("effort"):
reasoning_effort = reasoning_config["effort"]

_effort_clamp = {"minimal": "low"}
if "gpt-5.6" in (model or "").lower():
# Ultra is the Codex product tier; the Responses API wire value is max.
_effort_clamp["ultra"] = "max"
# "ultra" is Hermes-internal ladder vocabulary (the Codex product
# tier); no Responses-API backend accepts it verbatim, so the
# baseline maps it to its wire cap "max" for EVERY model — the old
# gpt-5.6-only guard leaked "ultra" untranslated to sibling models
# and the request 400'd (same class as #89503 on the
# chat-completions transport). Backend-specific branches below
# override the baseline where the ceiling is narrower.
_effort_clamp = {"minimal": "low", "ultra": "max"}
if params.get("is_xai_responses", False):
from agent.model_metadata import is_grok_46_family

Expand Down
38 changes: 22 additions & 16 deletions plugins/model-providers/copilot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,29 @@ def build_api_kwargs_extras(
effort = reasoning_config.get("effort", "medium")
# Honor the requested level when the live Copilot catalog
# lists it as supported: gpt-5.5/gpt-5.4 DO support
# ``xhigh``. Only downgrade levels the catalog does NOT
# list (e.g. ``xhigh``/``max`` on models capped lower, or
# ``minimal`` where unsupported), choosing the nearest
# weaker supported level rather than forwarding verbatim.
#
# (Previously this unconditionally mapped xhigh->high, a
# stale guard that silently capped models which do support
# the higher level.)
# ``xhigh``. Otherwise clamp to the nearest WEAKER
# supported level via the shared ladder helper — the old
# ad-hoc rules dropped everything unrecognized to
# ``medium``, which inverted the ladder: ``ultra`` (the
# strongest ask) resolved weaker than an explicit
# ``high`` (#74295).
if effort not in supported_efforts:
if effort == "xhigh" and "high" in supported_efforts:
effort = "high"
elif effort == "minimal" and "low" in supported_efforts:
effort = "low"
elif "medium" in supported_efforts:
effort = "medium"
else:
effort = supported_efforts[0]
from hermes_cli.models import (
clamp_reasoning_effort_to_supported,
)

effort = clamp_reasoning_effort_to_supported(
effort, list(supported_efforts)
)
if effort not in supported_efforts:
# Unrecognized/bespoke level the ladder can't
# place — fall back to medium, then to the
# catalog's first entry.
effort = (
"medium"
if "medium" in supported_efforts
else supported_efforts[0]
)
if effort in supported_efforts:
extra_body["reasoning"] = {"effort": effort}
elif supported_efforts:
Expand Down
8 changes: 7 additions & 1 deletion plugins/model-providers/custom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ def build_api_kwargs_extras(
top_level["reasoning_effort"] = "none"
extra_body["think"] = False
elif _effort:
top_level["reasoning_effort"] = _effort
# "ultra" is Hermes-internal ladder vocabulary — no known
# OpenAI-compatible backend accepts it verbatim (GLM/ARK,
# vLLM and SGLang all top out at "max"); cap it at the wire
# ceiling instead of forwarding a guaranteed 400 (#89503).
top_level["reasoning_effort"] = (
"max" if _effort == "ultra" else _effort
)

return extra_body, top_level

Expand Down
54 changes: 54 additions & 0 deletions tests/agent/test_reasoning_effort_wire_translation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Wire translation for Hermes' extended reasoning-effort vocabulary (#89503).

Hermes' internal effort set extends the wire vocabulary with ``ultra`` (the
/reasoning command documents none..xhigh|max|ultra). OpenAI-compatible wires —
OpenRouter chief among them — accept exactly max|xhigh|high|medium|low|minimal|
none and reject the extension with HTTP 400:

reasoning.effort: Invalid option: expected one of "max"|"xhigh"|"high"|
"medium"|"low"|"minimal"|"none"

An ``ultra`` configured while the default model was Anthropic worked (the
Anthropic adapter maps its own levels), but the moment a per-job override
pinned an OpenRouter model the extension leaked through the OpenAI-compatible
transport untranslated and every call failed. ``_reasoning_config_for_model``
is the wire-compat chokepoint for this transport: it must cap the extension
for every model, not just the one vendor prefix that happened to be fixed
first.
"""

from agent.transports.chat_completions import _reasoning_config_for_model


class TestUltraEffortWireTranslation:
def test_ultra_maps_to_max_for_any_model(self):
"""The extension level caps at the wire vocabulary for every model —
including the OpenRouter vendor prefixes a per-job override pins
(#89503's nvidia/ case) and models with no vendor prefix at all."""
for model in (
"nvidia/nemotron-3.5-lightning:free",
"deepseek/deepseek-v4",
"qwen/qwen3.5-coder",
"some-internal-model",
):
out = _reasoning_config_for_model(
model, {"enabled": True, "effort": "ultra"}
)
assert out == {"enabled": True, "effort": "max"}, model

def test_gpt_56_ultra_still_maps(self):
"""The original pre-existing mapping (gpt-5.6 + ultra → max) is
preserved by the generalized one."""
out = _reasoning_config_for_model(
"gpt-5.6", {"enabled": True, "effort": "ultra"}
)
assert out == {"enabled": True, "effort": "max"}

def test_wire_native_levels_pass_through_untouched(self):
for level in ("none", "minimal", "low", "medium", "high", "xhigh", "max"):
cfg = {"enabled": True, "effort": level}
assert _reasoning_config_for_model("any/model", cfg) == cfg

def test_non_dict_and_none_pass_through(self):
assert _reasoning_config_for_model("m", None) is None
assert _reasoning_config_for_model("m", "not-a-dict") == "not-a-dict"
121 changes: 121 additions & 0 deletions tests/agent/transports/test_reasoning_effort_sibling_sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Sibling-site coverage for the reasoning-effort wire-vocabulary class (#89503).

The chat-completions chokepoint fix (ultra → max for every model) is covered
by tests/agent/test_reasoning_effort_wire_translation.py. These tests pin the
sibling sites fixed in the same sweep:

- Kimi/Moonshot top-level ``reasoning_effort``: K3 accepts low/high/max only
(docs: default high); K2-era models accept low/medium/high. Previously the
transport forwarded only {low,medium,high} and silently dropped everything
else to "medium", so K3 400'd on "medium" requests and ultra resolved
WEAKER than an explicit high (ladder inversion).
- Tencent TokenHub: accepts low/medium/high; upper-ladder levels previously
dropped to the "high" default (accidentally right) but "minimal" also
dropped to high — asked for the least, got the most.
- Codex/Responses transport: ultra → max for EVERY model, not just gpt-5.6.
"""

from agent.transports import get_transport
import agent.transports.chat_completions # noqa: F401
import agent.transports.codex # noqa: F401


def _cc():
return get_transport("chat_completions")


def _kimi_kwargs(model, reasoning_config):
return _cc().build_kwargs(
model=model,
messages=[{"role": "user", "content": "hi"}],
is_kimi=True,
reasoning_config=reasoning_config,
)


class TestKimiEffortVocabulary:
def test_k3_maps_full_hermes_ladder(self):
expected = {
"minimal": "low",
"low": "low",
"medium": "high",
"high": "high",
"xhigh": "max",
"max": "max",
"ultra": "max",
}
for hermes_level, wire_level in expected.items():
kw = _kimi_kwargs(
"kimi-k3", {"enabled": True, "effort": hermes_level}
)
assert kw["reasoning_effort"] == wire_level, hermes_level

def test_k3_default_is_high(self):
kw = _kimi_kwargs("kimi-k3", None)
assert kw["reasoning_effort"] == "high"

def test_k2_upper_ladder_caps_at_high_not_medium(self):
"""Pre-fix, ultra/max/xhigh on K2-era models silently dropped to the
'medium' default — the strongest ask resolved weaker than an explicit
high (ladder inversion, same class as #74295)."""
for level in ("xhigh", "max", "ultra"):
kw = _kimi_kwargs(
"moonshotai/kimi-k2.6", {"enabled": True, "effort": level}
)
assert kw["reasoning_effort"] == "high", level

def test_k2_native_levels_pass_through(self):
for level in ("low", "medium", "high"):
kw = _kimi_kwargs(
"moonshotai/kimi-k2.6", {"enabled": True, "effort": level}
)
assert kw["reasoning_effort"] == level

def test_k2_minimal_maps_to_low(self):
kw = _kimi_kwargs(
"moonshotai/kimi-k2.6", {"enabled": True, "effort": "minimal"}
)
assert kw["reasoning_effort"] == "low"

def test_disabled_omits_effort(self):
kw = _kimi_kwargs("kimi-k3", {"enabled": False})
assert "reasoning_effort" not in kw


class TestTokenHubEffortVocabulary:
def _kwargs(self, reasoning_config):
return _cc().build_kwargs(
model="hunyuan-t2",
messages=[{"role": "user", "content": "hi"}],
is_tokenhub=True,
reasoning_config=reasoning_config,
)

def test_upper_ladder_caps_at_high(self):
for level in ("xhigh", "max", "ultra"):
kw = self._kwargs({"enabled": True, "effort": level})
assert kw["reasoning_effort"] == "high", level

def test_minimal_maps_to_low_not_high(self):
"""Pre-fix, 'minimal' fell through to the 'high' default — asked for
the least reasoning, got the most."""
kw = self._kwargs({"enabled": True, "effort": "minimal"})
assert kw["reasoning_effort"] == "low"

def test_native_levels_pass_through(self):
for level in ("low", "medium", "high"):
kw = self._kwargs({"enabled": True, "effort": level})
assert kw["reasoning_effort"] == level


class TestCodexUltraForEveryModel:
def test_ultra_maps_to_max_for_non_gpt56_models(self):
transport = get_transport("codex_responses")
for model in ("gpt-5.6-codex", "o5-pro", "some-responses-model"):
kw = transport.build_kwargs(
model=model,
messages=[{"role": "user", "content": "hi"}],
tools=[],
reasoning_config={"enabled": True, "effort": "ultra"},
)
assert kw["reasoning"]["effort"] == "max", model
Loading