diff --git a/batch_runner.py b/batch_runner.py index 32cd203b2..195452c0a 100644 --- a/batch_runner.py +++ b/batch_runner.py @@ -1158,7 +1158,7 @@ def main( providers_order (str): Comma-separated list of OpenRouter providers to try in order (e.g. "anthropic,openai,google") provider_sort (str): Sort providers by "price", "throughput", or "latency" (OpenRouter only) max_tokens (int): Maximum tokens for model responses (optional, uses model default if not set) - reasoning_effort (str): OpenRouter reasoning effort level: "xhigh", "high", "medium", "low", "minimal", "none" (default: "medium") + reasoning_effort (str): OpenRouter reasoning effort level: "none", "minimal", "low", "medium", "high", "xhigh" (default: "medium") reasoning_disabled (bool): Completely disable reasoning/thinking tokens (default: False) prefill_messages_file (str): Path to JSON file containing prefill messages (list of {role, content} dicts) max_samples (int): Only process the first N samples from the dataset (optional, processes all if not set) @@ -1227,7 +1227,7 @@ def main( print("🧠 Reasoning: DISABLED (effort=none)") elif reasoning_effort: # Use specified effort level - valid_efforts = ["xhigh", "high", "medium", "low", "minimal", "none"] + valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh"] if reasoning_effort not in valid_efforts: print(f"❌ Error: --reasoning_effort must be one of: {', '.join(valid_efforts)}") return diff --git a/cli.py b/cli.py index db956766b..30e43b6ed 100644 --- a/cli.py +++ b/cli.py @@ -5259,7 +5259,7 @@ class HermesCLI: Usage: /reasoning Show current effort level and display state - /reasoning Set reasoning effort (none, low, medium, high, xhigh) + /reasoning Set reasoning effort (none, minimal, low, medium, high, xhigh) /reasoning show|on Show model thinking/reasoning in output /reasoning hide|off Hide model thinking/reasoning from output """ @@ -5277,7 +5277,7 @@ class HermesCLI: display_state = "on ✓" if self.show_reasoning else "off" _cprint(f" {_GOLD}Reasoning effort: {level}{_RST}") _cprint(f" {_GOLD}Reasoning display: {display_state}{_RST}") - _cprint(f" {_DIM}Usage: /reasoning {_RST}") + _cprint(f" {_DIM}Usage: /reasoning {_RST}") return arg = parts[1].strip().lower() @@ -5303,7 +5303,7 @@ class HermesCLI: parsed = _parse_reasoning_config(arg) if parsed is None: _cprint(f" {_DIM}(._.) Unknown argument: {arg}{_RST}") - _cprint(f" {_DIM}Valid levels: none, low, minimal, medium, high, xhigh{_RST}") + _cprint(f" {_DIM}Valid levels: none, minimal, low, medium, high, xhigh{_RST}") _cprint(f" {_DIM}Display: show, hide{_RST}") return diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 686d60618..a19b6d666 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -1595,7 +1595,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: xhigh, high, medium, low, minimal, or none.") + @discord.app_commands.describe(effort="Reasoning effort: none, minimal, low, medium, high, or xhigh.") 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 339954f5b..abdb3324c 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -4840,7 +4840,7 @@ class GatewayRunner: Usage: /reasoning Show current effort level and display state - /reasoning Set reasoning effort (none, low, medium, high, xhigh) + /reasoning Set reasoning effort (none, minimal, low, medium, high, xhigh) /reasoning show|on Show model reasoning in responses /reasoning hide|off Hide model reasoning from responses """ @@ -4885,7 +4885,7 @@ class GatewayRunner: "🧠 **Reasoning Settings**\n\n" f"**Effort:** `{level}`\n" f"**Display:** {display_state}\n\n" - "_Usage:_ `/reasoning `" + "_Usage:_ `/reasoning `" ) # Display toggle @@ -4908,7 +4908,7 @@ class GatewayRunner: else: return ( f"⚠️ Unknown argument: `{effort}`\n\n" - "**Valid levels:** none, low, minimal, medium, high, xhigh\n" + "**Valid levels:** none, minimal, low, medium, high, xhigh\n" "**Display:** show, hide" ) diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index ac0f44d74..5231dccb8 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -99,7 +99,7 @@ COMMAND_REGISTRY: list[CommandDef] = [ "Configuration"), CommandDef("reasoning", "Manage reasoning effort and display", "Configuration", args_hint="[level|show|hide]", - subcommands=("none", "low", "minimal", "medium", "high", "xhigh", "show", "hide", "on", "off")), + subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "show", "hide", "on", "off")), CommandDef("skin", "Show or change the display skin/theme", "Configuration", cli_only=True, args_hint="[name]"), CommandDef("voice", "Toggle voice mode", "Configuration", diff --git a/hermes_cli/main.py b/hermes_cli/main.py index a6d616e68..1e7be0543 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -1811,7 +1811,10 @@ def _set_reasoning_effort(config, effort: str) -> None: def _prompt_reasoning_effort_selection(efforts, current_effort=""): """Prompt for a reasoning effort. Returns effort, 'none', or None to keep current.""" - ordered = list(dict.fromkeys(str(effort).strip().lower() for effort in efforts if str(effort).strip())) + deduped = list(dict.fromkeys(str(effort).strip().lower() for effort in efforts if str(effort).strip())) + canonical_order = ("minimal", "low", "medium", "high", "xhigh") + 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: return None diff --git a/hermes_constants.py b/hermes_constants.py index c28f6dc8f..eded659ec 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -78,7 +78,7 @@ VALID_REASONING_EFFORTS = ("xhigh", "high", "medium", "low", "minimal") def parse_reasoning_effort(effort: str) -> dict | None: """Parse a reasoning effort level into a config dict. - Valid levels: "xhigh", "high", "medium", "low", "minimal", "none". + Valid levels: "none", "minimal", "low", "medium", "high", "xhigh". 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/hermes_cli/test_commands.py b/tests/hermes_cli/test_commands.py index 98a4b2efc..29996fe18 100644 --- a/tests/hermes_cli/test_commands.py +++ b/tests/hermes_cli/test_commands.py @@ -68,6 +68,17 @@ class TestCommandRegistry: for cmd in COMMAND_REGISTRY: assert cmd.category in valid_categories, f"{cmd.name} has invalid category '{cmd.category}'" + def test_reasoning_subcommands_are_in_logical_order(self): + reasoning = next(cmd for cmd in COMMAND_REGISTRY if cmd.name == "reasoning") + assert reasoning.subcommands[:6] == ( + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + ) + def test_cli_only_and_gateway_only_are_mutually_exclusive(self): for cmd in COMMAND_REGISTRY: assert not (cmd.cli_only and cmd.gateway_only), \ diff --git a/tests/hermes_cli/test_reasoning_effort_menu.py b/tests/hermes_cli/test_reasoning_effort_menu.py new file mode 100644 index 000000000..3d360a4f2 --- /dev/null +++ b/tests/hermes_cli/test_reasoning_effort_menu.py @@ -0,0 +1,34 @@ +import sys +import types + + +from hermes_cli.main import _prompt_reasoning_effort_selection + + +class _FakeTerminalMenu: + last_choices = None + + def __init__(self, choices, **kwargs): + _FakeTerminalMenu.last_choices = choices + self._cursor_index = kwargs.get("cursor_index") + + def show(self): + return self._cursor_index + + +def test_reasoning_menu_orders_minimal_before_low(monkeypatch): + fake_module = types.SimpleNamespace(TerminalMenu=_FakeTerminalMenu) + monkeypatch.setitem(sys.modules, "simple_term_menu", fake_module) + + selected = _prompt_reasoning_effort_selection( + ["low", "minimal", "medium", "high"], + current_effort="medium", + ) + + assert selected == "medium" + assert _FakeTerminalMenu.last_choices[:4] == [ + " minimal", + " low", + " medium ← currently in use", + " high", + ]