diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 3eaf6ac05..659a42e77 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -2194,7 +2194,7 @@ class DiscordAdapter(BasePlatformAdapter): await self._run_simple_slash(interaction, f"/model {name}".strip()) @tree.command(name="reasoning", description="Show or change reasoning effort") - @discord.app_commands.describe(effort="Reasoning effort: none, minimal, low, medium, high, or xhigh.") + @discord.app_commands.describe(effort="Reasoning effort: none, minimal, low, medium, high, xhigh, or max.") async def slash_reasoning(interaction: discord.Interaction, effort: str = ""): await self._run_simple_slash(interaction, f"/reasoning {effort}".strip()) diff --git a/gateway/run.py b/gateway/run.py index f5c1858db..9e9976581 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -1367,7 +1367,7 @@ class GatewayRunner: """Load reasoning effort from config.yaml. Reads agent.reasoning_effort from config.yaml. Valid: "none", - "minimal", "low", "medium", "high", "xhigh". Returns None to use + "minimal", "low", "medium", "high", "xhigh", "max". Returns None to use default (medium). """ from hermes_constants import parse_reasoning_effort @@ -6830,7 +6830,7 @@ class GatewayRunner: Usage: /reasoning Show current effort level and display state - /reasoning Set reasoning effort (none, minimal, low, medium, high, xhigh) + /reasoning Set reasoning effort (none, minimal, low, medium, high, xhigh, max) /reasoning show|on Show model reasoning in responses /reasoning hide|off Hide model reasoning from responses """ @@ -6875,7 +6875,7 @@ class GatewayRunner: "🧠 **Reasoning Settings**\n\n" f"**Effort:** `{level}`\n" f"**Display:** {display_state}\n\n" - "_Usage:_ `/reasoning `" + "_Usage:_ `/reasoning `" ) # Display toggle (per-platform) @@ -6895,14 +6895,15 @@ class GatewayRunner: # Effort level change effort = args.strip() + from hermes_constants import VALID_REASONING_EFFORTS if effort == "none": parsed = {"enabled": False} - elif effort in ("minimal", "low", "medium", "high", "xhigh"): + elif effort in VALID_REASONING_EFFORTS: parsed = {"enabled": True, "effort": effort} else: return ( f"⚠️ Unknown argument: `{effort}`\n\n" - "**Valid levels:** none, minimal, low, medium, high, xhigh\n" + f"**Valid levels:** none, {', '.join(VALID_REASONING_EFFORTS)}\n" "**Display:** show, hide" ) diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index efff57180..f9f7998d2 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -118,7 +118,7 @@ COMMAND_REGISTRY: list[CommandDef] = [ "Configuration"), CommandDef("reasoning", "Manage reasoning effort and display", "Configuration", args_hint="[level|show|hide]", - subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "show", "hide", "on", "off")), + subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "max", "show", "hide", "on", "off")), CommandDef("fast", "Toggle fast mode — OpenAI Priority Processing / Anthropic Fast Mode (Normal/Fast)", "Configuration", args_hint="[normal|fast|status]", subcommands=("normal", "fast", "status", "on", "off")), diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 7de68d2cb..6879d2a0e 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3064,7 +3064,7 @@ def _prompt_reasoning_effort_selection(efforts, current_effort=""): str(effort).strip().lower() for effort in efforts if str(effort).strip() ) ) - canonical_order = ("minimal", "low", "medium", "high", "xhigh") + canonical_order = ("minimal", "low", "medium", "high", "xhigh", "max") ordered = [effort for effort in canonical_order if effort in deduped] ordered.extend(effort for effort in deduped if effort not in canonical_order) if not ordered: diff --git a/hermes_constants.py b/hermes_constants.py index 35dbf86ab..26fd66b73 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -138,13 +138,13 @@ def get_subprocess_home() -> str | None: return None -VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh") +VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh", "max") def parse_reasoning_effort(effort: str) -> dict | None: """Parse a reasoning effort level into a config dict. - Valid levels: "none", "minimal", "low", "medium", "high", "xhigh". + Valid levels: "none", "minimal", "low", "medium", "high", "xhigh", "max". Returns None when the input is empty or unrecognized (caller uses default). Returns {"enabled": False} for "none". Returns {"enabled": True, "effort": } for valid effort levels. diff --git a/tests/test_hermes_constants.py b/tests/test_hermes_constants.py index d49dff813..a095c00f0 100644 --- a/tests/test_hermes_constants.py +++ b/tests/test_hermes_constants.py @@ -111,3 +111,47 @@ class TestIsContainer: # Even if we make os.path.exists return False, cached value wins monkeypatch.setattr(os.path, "exists", lambda p: False) assert is_container() is True + + +class TestParseReasoningEffort: + """Tests for parse_reasoning_effort() — reasoning effort parsing and validation.""" + + def test_empty_string_returns_none(self): + """Empty input defers to caller's default.""" + from hermes_constants import parse_reasoning_effort + assert parse_reasoning_effort("") is None + assert parse_reasoning_effort(" ") is None + + def test_none_disables_reasoning(self): + """'none' explicitly disables reasoning.""" + from hermes_constants import parse_reasoning_effort + assert parse_reasoning_effort("none") == {"enabled": False} + assert parse_reasoning_effort("NONE") == {"enabled": False} + + def test_standard_levels(self): + """Each canonical effort level round-trips correctly.""" + from hermes_constants import parse_reasoning_effort + for level in ("minimal", "low", "medium", "high", "xhigh"): + assert parse_reasoning_effort(level) == {"enabled": True, "effort": level} + + def test_max_level_accepted(self): + """'max' is the strongest adaptive-thinking level on Claude 4.6+/4.7.""" + from hermes_constants import parse_reasoning_effort + assert parse_reasoning_effort("max") == {"enabled": True, "effort": "max"} + + def test_case_insensitive(self): + """Effort strings are case-insensitive.""" + from hermes_constants import parse_reasoning_effort + assert parse_reasoning_effort("MAX") == {"enabled": True, "effort": "max"} + assert parse_reasoning_effort("XHigh") == {"enabled": True, "effort": "xhigh"} + + def test_unknown_level_returns_none(self): + """Unrecognized levels return None so caller can fall back to default.""" + from hermes_constants import parse_reasoning_effort + assert parse_reasoning_effort("extreme") is None + assert parse_reasoning_effort("ultra") is None + + def test_valid_reasoning_efforts_includes_max(self): + """VALID_REASONING_EFFORTS exposes 'max' as a supported level.""" + from hermes_constants import VALID_REASONING_EFFORTS + assert "max" in VALID_REASONING_EFFORTS