diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 21586830634..689d01010ad 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -65,6 +65,7 @@ THINKING_BUDGET = {"xhigh": 32000, "high": 16000, "medium": 8000, "low": 4000} # maps to low on every model. See: # https://platform.claude.com/docs/en/about-claude/models/migration-guide ADAPTIVE_EFFORT_MAP = { + "ultra": "max", "max": "max", "xhigh": "xhigh", "high": "high", diff --git a/agent/transports/chat_completions.py b/agent/transports/chat_completions.py index 6b8dea04e30..8d94ecbcbed 100644 --- a/agent/transports/chat_completions.py +++ b/agent/transports/chat_completions.py @@ -18,6 +18,20 @@ from agent.transports.base import ProviderTransport from agent.transports.types import NormalizedResponse, ToolCall, Usage +def _reasoning_config_for_model(model: str, reasoning_config: dict | None) -> dict | None: + """Return the model's wire-compatible reasoning config.""" + 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" + ): + normalized = dict(reasoning_config) + normalized["effort"] = "max" + return normalized + return reasoning_config + + def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) -> dict | None: """Translate Hermes/OpenRouter-style reasoning config to Gemini thinkingConfig.""" if reasoning_config is None or not isinstance(reasoning_config, dict): @@ -52,7 +66,7 @@ def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) -> if normalized_model.startswith("gemini-2.5-"): return thinking_config - if effort not in {"minimal", "low", "medium", "high", "xhigh"}: + if effort not in {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"}: effort = "medium" # Gemini 3 Flash documents low/medium/high thinking levels; Gemini 3 Pro @@ -62,13 +76,13 @@ def _build_gemini_thinking_config(model: str, reasoning_config: dict | None) -> if "flash" in normalized_model: if effort in {"minimal", "low"}: thinking_config["thinkingLevel"] = "low" - elif effort in {"high", "xhigh"}: + elif effort in {"high", "xhigh", "max", "ultra"}: thinking_config["thinkingLevel"] = "high" else: thinking_config["thinkingLevel"] = "medium" elif "pro" in normalized_model: thinking_config["thinkingLevel"] = ( - "high" if effort in {"high", "xhigh"} else "low" + "high" if effort in {"high", "xhigh", "max", "ultra"} else "low" ) return thinking_config @@ -364,7 +378,7 @@ class ChatCompletionsTransport(ProviderTransport): is_nvidia_nim = params.get("is_nvidia_nim", False) is_kimi = params.get("is_kimi", False) is_tokenhub = params.get("is_tokenhub", False) - reasoning_config = params.get("reasoning_config") + reasoning_config = _reasoning_config_for_model(model, params.get("reasoning_config")) if ephemeral is not None and max_tokens_fn: api_kwargs.update(max_tokens_fn(ephemeral)) @@ -563,7 +577,7 @@ class ChatCompletionsTransport(ProviderTransport): api_kwargs["max_tokens"] = anthropic_max # Provider-specific api_kwargs extras (reasoning_effort, metadata, etc.) - reasoning_config = params.get("reasoning_config") + reasoning_config = _reasoning_config_for_model(model, params.get("reasoning_config")) extra_body_from_profile, top_level_from_profile = ( profile.build_api_kwargs_extras( reasoning_config=reasoning_config, diff --git a/agent/transports/codex.py b/agent/transports/codex.py index a592df35035..56f25963222 100644 --- a/agent/transports/codex.py +++ b/agent/transports/codex.py @@ -164,6 +164,12 @@ class ResponsesApiTransport(ProviderTransport): 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" + if params.get("is_xai_responses", False): + # xAI Responses tops out at high; keep generic stronger values usable. + _effort_clamp.update({"xhigh": "high", "max": "high", "ultra": "high"}) reasoning_effort = _effort_clamp.get(reasoning_effort, reasoning_effort) response_tools = _responses_tools(tools) diff --git a/apps/desktop/src/app/settings/constants.ts b/apps/desktop/src/app/settings/constants.ts index 5ae4eb393f5..8332ff82055 100644 --- a/apps/desktop/src/app/settings/constants.ts +++ b/apps/desktop/src/app/settings/constants.ts @@ -237,7 +237,7 @@ export const ENUM_OPTIONS: Record = { 'approvals.mode': ['manual', 'smart', 'off'], 'code_execution.mode': ['project', 'strict'], 'context.engine': ['compressor', 'default', 'custom'], - 'delegation.reasoning_effort': ['', 'minimal', 'low', 'medium', 'high', 'xhigh'], + 'delegation.reasoning_effort': ['', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'], 'memory.provider': ['', 'builtin', 'hindsight', 'honcho'], // Terminal execution backends — kept in sync with the dispatch ladder in // tools/terminal_tool.py::_create_environment (local/docker/singularity/ diff --git a/apps/desktop/src/app/settings/model-settings.tsx b/apps/desktop/src/app/settings/model-settings.tsx index 01c57f9925b..040781942e2 100644 --- a/apps/desktop/src/app/settings/model-settings.tsx +++ b/apps/desktop/src/app/settings/model-settings.tsx @@ -82,7 +82,7 @@ export function ModelSettingsSkeleton() { // Hermes' reasoning levels (VALID_REASONING_EFFORTS); `none` = thinking off. // Empty config = Hermes default (medium), shown as Medium. -const EFFORT_VALUES = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'] as const +const EFFORT_VALUES = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const // agent.service_tier stores "fast"/"priority"/"on" for fast; anything else is // normal (mirrors tui_gateway _load_service_tier). @@ -93,8 +93,8 @@ const isFastTier = (tier: unknown): boolean => .toLowerCase() ) -// Reuse the composer's effort labels (`xhigh` shows as "Max", else 1:1). -const effortLabelKey = (v: string) => (v === 'xhigh' ? 'max' : v) as 'high' | 'low' | 'max' | 'medium' | 'minimal' +// Reuse the composer's effort labels. +const effortLabelKey = (v: string) => v as 'high' | 'low' | 'max' | 'medium' | 'minimal' | 'ultra' | 'xhigh' // A provider row is "ready" to pick a model from when it reports models. The // backend now surfaces the full `hermes model` universe (every canonical diff --git a/apps/desktop/src/app/shell/model-edit-submenu.tsx b/apps/desktop/src/app/shell/model-edit-submenu.tsx index cf2a8af660f..dda409699f5 100644 --- a/apps/desktop/src/app/shell/model-edit-submenu.tsx +++ b/apps/desktop/src/app/shell/model-edit-submenu.tsx @@ -24,7 +24,9 @@ const EFFORT_OPTIONS = [ { value: 'low', labelKey: 'low' }, { value: 'medium', labelKey: 'medium' }, { value: 'high', labelKey: 'high' }, - { value: 'xhigh', labelKey: 'max' } + { value: 'xhigh', labelKey: 'xhigh' }, + { value: 'max', labelKey: 'max' }, + { value: 'ultra', labelKey: 'ultra' } ] as const /** How "fast" is achieved for a given model — two different mechanisms: diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index f4837ea8169..724537bc328 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -2039,7 +2039,9 @@ export const en: Translations = { low: 'Low', medium: 'Medium', high: 'High', + xhigh: 'Extra High', max: 'Max', + ultra: 'Ultra', updateFailed: 'Model option update failed', fastFailed: 'Fast mode update failed' }, diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index bbfa6963cad..1d08b56ea6c 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -1984,7 +1984,9 @@ export const ja = defineLocale({ low: '低', medium: '中', high: '高', + xhigh: '特高', max: '最大', + ultra: 'ウルトラ', updateFailed: 'モデルオプションの更新に失敗しました', fastFailed: '高速モードの更新に失敗しました' }, diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index 917609e7df5..ca2ef91def4 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -1671,7 +1671,9 @@ export interface Translations { low: string medium: string high: string + xhigh: string max: string + ultra: string updateFailed: string fastFailed: string } diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index 37ad10e069f..10c571693b8 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -1919,7 +1919,9 @@ export const zhHant = defineLocale({ low: '低', medium: '中', high: '高', + xhigh: '極高', max: '最高', + ultra: '超高', updateFailed: '模型選項更新失敗', fastFailed: '快速模式更新失敗' }, diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index dc9524c07c5..e078c42f7dc 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -2203,7 +2203,9 @@ export const zh: Translations = { low: '低', medium: '中', high: '高', + xhigh: '极高', max: '最高', + ultra: '超高', updateFailed: '模型选项更新失败', fastFailed: '快速模式更新失败' }, diff --git a/apps/desktop/src/lib/model-status-label.test.ts b/apps/desktop/src/lib/model-status-label.test.ts index 4e22d9fb974..cc499c86d56 100644 --- a/apps/desktop/src/lib/model-status-label.test.ts +++ b/apps/desktop/src/lib/model-status-label.test.ts @@ -22,7 +22,9 @@ describe('model-status-label', () => { it('maps reasoning effort to compact labels', () => { expect(reasoningEffortLabel('high')).toBe('High') - expect(reasoningEffortLabel('xhigh')).toBe('Max') + expect(reasoningEffortLabel('xhigh')).toBe('XHigh') + expect(reasoningEffortLabel('max')).toBe('Max') + expect(reasoningEffortLabel('ultra')).toBe('Ultra') expect(reasoningEffortLabel('')).toBe('') }) diff --git a/apps/desktop/src/lib/model-status-label.ts b/apps/desktop/src/lib/model-status-label.ts index 27a4d202b7d..a1b28b8a4c5 100644 --- a/apps/desktop/src/lib/model-status-label.ts +++ b/apps/desktop/src/lib/model-status-label.ts @@ -6,7 +6,9 @@ const REASONING_LABELS: Record = { low: 'Low', medium: 'Med', high: 'High', - xhigh: 'Max' + xhigh: 'XHigh', + max: 'Max', + ultra: 'Ultra' } export function reasoningEffortLabel(effort: string): string { diff --git a/batch_runner.py b/batch_runner.py index 28936198955..f7fc28d7a5c 100644 --- a/batch_runner.py +++ b/batch_runner.py @@ -1192,7 +1192,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: "none", "minimal", "low", "medium", "high", "xhigh" (default: "medium") + reasoning_effort (str): Reasoning effort: "none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra" (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) @@ -1261,7 +1261,7 @@ def main( print("🧠 Reasoning: DISABLED (effort=none)") elif reasoning_effort: # Use specified effort level - valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh"] + valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"] if reasoning_effort not in valid_efforts: print(f"❌ Error: --reasoning_effort must be one of: {', '.join(valid_efforts)}") return diff --git a/gateway/run.py b/gateway/run.py index 6e5d8eb5d66..ef49bf67c47 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -4792,7 +4792,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew """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", "ultra". Returns None to use default (medium). """ from hermes_constants import parse_reasoning_effort diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index 8d4eee356f9..38ab051c8e4 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -2733,7 +2733,7 @@ class GatewaySlashCommandsMixin: return t("gateway.reasoning.reset_done") if effort == "none": parsed = {"enabled": False} - elif effort in {"minimal", "low", "medium", "high", "xhigh"}: + elif effort in {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"}: parsed = {"enabled": True, "effort": effort} else: return t( diff --git a/hermes_cli/cli_commands_mixin.py b/hermes_cli/cli_commands_mixin.py index b16d2166e2c..c8bf1e67136 100644 --- a/hermes_cli/cli_commands_mixin.py +++ b/hermes_cli/cli_commands_mixin.py @@ -2471,7 +2471,7 @@ class CLICommandsMixin: Usage: /reasoning Show current effort level and display state - /reasoning Set reasoning effort (none, minimal, low, medium, high, xhigh) + /reasoning Set effort (none, minimal, low, medium, high, xhigh, max, ultra) /reasoning show|on Show model thinking/reasoning in output /reasoning hide|off Hide model thinking/reasoning from output /reasoning full Show complete thinking (no 10-line clamp) @@ -2493,7 +2493,7 @@ class CLICommandsMixin: full_state = "full" if getattr(self, "reasoning_full", False) else "clamped to 10 lines" _cprint(f" {_ACCENT}Reasoning effort: {level}{_RST}") _cprint(f" {_ACCENT}Reasoning display: {display_state} ({full_state}){_RST}") - _cprint(f" {_DIM}Usage: /reasoning {_RST}") + _cprint(f" {_DIM}Usage: /reasoning {_RST}") return arg = parts[1].strip().lower() @@ -2534,7 +2534,7 @@ class CLICommandsMixin: parsed = _parse_reasoning_config(arg) if parsed is None: _cprint(f" {_DIM}(._.) Unknown argument: {arg}{_RST}") - _cprint(f" {_DIM}Valid levels: none, minimal, low, medium, high, xhigh{_RST}") + _cprint(f" {_DIM}Valid levels: none, minimal, low, medium, high, xhigh, max, ultra{_RST}") _cprint(f" {_DIM}Display: show, hide{_RST}") return diff --git a/hermes_cli/commands.py b/hermes_cli/commands.py index 3e2d03dc358..4f32f7315cb 100644 --- a/hermes_cli/commands.py +++ b/hermes_cli/commands.py @@ -154,7 +154,7 @@ COMMAND_REGISTRY: list[CommandDef] = [ "Configuration"), CommandDef("reasoning", "Manage reasoning effort and display", "Configuration", args_hint="[level|show|hide|full|clamp]", - subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "show", "hide", "on", "off", "full", "clamp")), + subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra", "show", "hide", "on", "off", "full", "clamp")), 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/config.py b/hermes_cli/config.py index 46839ca18f7..03570bceba4 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -2240,8 +2240,8 @@ DEFAULT_CONFIG = { # (API, tools, iteration budget), never a delegation # stopwatch. Set a positive number of seconds # (floor 30s) to enforce a hard cap. - "reasoning_effort": "", # reasoning effort for subagents: "xhigh", "high", "medium", - # "low", "minimal", "none" (empty = inherit parent's level) + "reasoning_effort": "", # subagent effort: "ultra", "max", "xhigh", "high", + # "medium", "low", "minimal", "none" (empty = inherit) "max_concurrent_children": 3, # unified concurrency cap: max parallel children per batch # AND max concurrent background (background=true) # delegation units. New async dispatches beyond the cap diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 9e5267fe428..2dc6f05e1b4 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3925,7 +3925,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", "ultra") 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_cli/web_server.py b/hermes_cli/web_server.py index ecd47ab2943..d6fa607ac91 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -696,7 +696,7 @@ _SCHEMA_OVERRIDES: Dict[str, Dict[str, Any]] = { "delegation.reasoning_effort": { "type": "select", "description": "Reasoning effort for delegated subagents", - "options": ["", "low", "medium", "high"], + "options": ["", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"], }, "updates.non_interactive_local_changes": { "type": "select", diff --git a/hermes_constants.py b/hermes_constants.py index 29dac85fe8a..a705918ef0a 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -791,13 +791,16 @@ def apply_subprocess_home_env(env: dict[str, str]) -> None: env["HOME"] = home -VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh", "max") +VALID_REASONING_EFFORTS = ( + "minimal", "low", "medium", "high", "xhigh", "max", "ultra", +) def parse_reasoning_effort(effort) -> dict | None: """Parse a reasoning effort level into a config dict. - Valid levels: "none", "minimal", "low", "medium", "high", "xhigh", "max". + Valid levels: "none", "minimal", "low", "medium", "high", "xhigh", "max", + "ultra". Returns None when the input is empty or unrecognized (caller uses default). Returns {"enabled": False} for "none" (aliases: "false", "disabled", and YAML boolean False — users write ``reasoning_effort: false``/``off``/``no`` diff --git a/infographic/reasoning-max-ultra/infographic.png b/infographic/reasoning-max-ultra/infographic.png new file mode 100644 index 00000000000..3856b5f9716 Binary files /dev/null and b/infographic/reasoning-max-ultra/infographic.png differ diff --git a/plugins/model-providers/copilot/__init__.py b/plugins/model-providers/copilot/__init__.py index d4409c108d0..f5c6ea865c4 100644 --- a/plugins/model-providers/copilot/__init__.py +++ b/plugins/model-providers/copilot/__init__.py @@ -35,8 +35,8 @@ class CopilotProfile(ProviderProfile): supported_efforts = github_model_reasoning_efforts(model) if supported_efforts and reasoning_config: effort = reasoning_config.get("effort", "medium") - # Normalize non-standard effort levels to the nearest supported - if effort == "xhigh": + # Normalize stronger generic levels to the nearest supported. + if effort in {"xhigh", "max", "ultra"}: effort = "high" if effort in supported_efforts: extra_body["reasoning"] = {"effort": effort} diff --git a/plugins/model-providers/deepseek/__init__.py b/plugins/model-providers/deepseek/__init__.py index 34a8017b76e..8656552e0dd 100644 --- a/plugins/model-providers/deepseek/__init__.py +++ b/plugins/model-providers/deepseek/__init__.py @@ -69,12 +69,12 @@ class DeepSeekProfile(ProviderProfile): if not enabled: return extra_body, top_level - # Effort mapping. Pass low/medium/high through; xhigh/max → max. + # Effort mapping. Pass low/medium/high through; stronger levels → max. # When no effort is set we omit reasoning_effort so DeepSeek applies # its server default (currently high). if isinstance(reasoning_config, dict): effort = (reasoning_config.get("effort") or "").strip().lower() - if effort in {"xhigh", "max"}: + if effort in {"xhigh", "max", "ultra"}: top_level["reasoning_effort"] = "max" elif effort in {"low", "medium", "high"}: top_level["reasoning_effort"] = effort diff --git a/plugins/model-providers/ollama-cloud/__init__.py b/plugins/model-providers/ollama-cloud/__init__.py index 7f04cd03ce5..a7856124b98 100644 --- a/plugins/model-providers/ollama-cloud/__init__.py +++ b/plugins/model-providers/ollama-cloud/__init__.py @@ -51,7 +51,7 @@ class OllamaCloudProfile(ProviderProfile): return {}, {} if effort == "none": return {}, {} # explicit none → suppress thinking - if effort in ("xhigh", "max"): + if effort in ("xhigh", "max", "ultra"): top_level["reasoning_effort"] = "max" elif effort in ("low", "medium", "high"): top_level["reasoning_effort"] = effort diff --git a/plugins/model-providers/opencode-zen/__init__.py b/plugins/model-providers/opencode-zen/__init__.py index f8cbcc1baf3..9b531264478 100644 --- a/plugins/model-providers/opencode-zen/__init__.py +++ b/plugins/model-providers/opencode-zen/__init__.py @@ -73,7 +73,7 @@ class OpenCodeGoProfile(ProviderProfile): effort = (reasoning_config.get("effort") or "").strip().lower() if not effort or effort == "none": return extra_body, top_level - top_level["reasoning_effort"] = "max" if effort in {"xhigh", "max"} else "high" + top_level["reasoning_effort"] = "max" if effort in {"xhigh", "max", "ultra"} else "high" return extra_body, top_level if _is_kimi_k2_model(model): @@ -90,7 +90,7 @@ class OpenCodeGoProfile(ProviderProfile): return extra_body, top_level effort = (reasoning_config.get("effort") or "").strip().lower() - if effort in {"xhigh", "max"}: + if effort in {"xhigh", "max", "ultra"}: top_level["reasoning_effort"] = "high" elif effort in {"low", "medium", "high"}: top_level["reasoning_effort"] = effort @@ -114,7 +114,7 @@ class OpenCodeGoProfile(ProviderProfile): if isinstance(reasoning_config, dict): effort = (reasoning_config.get("effort") or "").strip().lower() - if effort in {"xhigh", "max"}: + if effort in {"xhigh", "max", "ultra"}: top_level["reasoning_effort"] = "max" elif effort in {"low", "medium", "high"}: top_level["reasoning_effort"] = effort diff --git a/plugins/model-providers/zai/__init__.py b/plugins/model-providers/zai/__init__.py index a4c022ff855..76426b77dcd 100644 --- a/plugins/model-providers/zai/__init__.py +++ b/plugins/model-providers/zai/__init__.py @@ -62,7 +62,7 @@ def _is_glm_5_2(model: str | None) -> bool: def _glm_5_2_reasoning_effort(reasoning_config: dict | None) -> str | None: """Map Hermes reasoning effort onto GLM-5.2's native ``high``/``max``. - GLM-5.2 only supports two enabled effort levels. ``xhigh``/``max`` + GLM-5.2 only supports two enabled effort levels. ``xhigh``/``max``/``ultra`` request the top tier; everything else that is enabled requests ``high`` (its minimum thinking level). When reasoning is explicitly disabled, or no effort preference is supplied, the server default is left untouched. @@ -76,7 +76,7 @@ def _glm_5_2_reasoning_effort(reasoning_config: dict | None) -> str | None: if not effort or effort == "none": return None - if effort in {"xhigh", "max"}: + if effort in {"xhigh", "max", "ultra"}: return "max" # low / medium / minimal / high all clamp to GLM-5.2's minimum: high. return "high" diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index 3d4ebbfb9a5..d162f2afc4a 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -4085,7 +4085,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="Effort: none, minimal, low, medium, high, xhigh, max, or ultra.") async def slash_reasoning(interaction: discord.Interaction, effort: str = ""): await self._run_simple_slash(interaction, f"/reasoning {effort}".strip()) diff --git a/skills/autonomous-ai-agents/hermes-agent/SKILL.md b/skills/autonomous-ai-agents/hermes-agent/SKILL.md index 8413e4cf208..5ba4ee874b3 100644 --- a/skills/autonomous-ai-agents/hermes-agent/SKILL.md +++ b/skills/autonomous-ai-agents/hermes-agent/SKILL.md @@ -294,7 +294,7 @@ The registry of record is `hermes_cli/commands.py` — every consumer /config Show config (CLI) /model [name] Show or change model /personality [name] Set personality -/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|show|hide) +/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|max|ultra|show|hide) /verbose Cycle: off → new → all → verbose /voice [on|off|tts] Voice mode /yolo Toggle approval bypass diff --git a/tests/agent/test_anthropic_adapter.py b/tests/agent/test_anthropic_adapter.py index 1393d71ab85..febdf53a338 100644 --- a/tests/agent/test_anthropic_adapter.py +++ b/tests/agent/test_anthropic_adapter.py @@ -1448,6 +1448,16 @@ class TestBuildAnthropicKwargs: assert kwargs["thinking"] == {"type": "adaptive", "display": "summarized"} assert kwargs["output_config"] == {"effort": "xhigh"} + def test_reasoning_config_clamps_generic_ultra_to_anthropic_max(self): + kwargs = build_anthropic_kwargs( + model="claude-opus-4.8", + messages=[{"role": "user", "content": "think harder"}], + tools=None, + max_tokens=4096, + reasoning_config={"enabled": True, "effort": "ultra"}, + ) + assert kwargs["output_config"] == {"effort": "max"} + def test_reasoning_config_maps_max_effort_for_4_7_models(self): kwargs = build_anthropic_kwargs( model="claude-opus-4-7", diff --git a/tests/agent/transports/test_chat_completions.py b/tests/agent/transports/test_chat_completions.py index f1eece83dad..487225ac404 100644 --- a/tests/agent/transports/test_chat_completions.py +++ b/tests/agent/transports/test_chat_completions.py @@ -21,6 +21,23 @@ class TestChatCompletionsBasic: def test_registered(self, transport): assert transport is not None + @pytest.mark.parametrize("provider", ["nous", "openrouter"]) + def test_gpt56_ultra_uses_max_wire_effort(self, transport, provider): + from providers import get_provider_profile + + profile = get_provider_profile(provider) + kw = transport.build_kwargs( + model="openai/gpt-5.6-sol", + messages=[{"role": "user", "content": "Hi"}], + tools=[], + reasoning_config={"enabled": True, "effort": "ultra"}, + supports_reasoning=True, + provider_profile=profile, + provider_name=provider, + base_url=profile.base_url, + ) + assert kw["extra_body"]["reasoning"] == {"enabled": True, "effort": "max"} + def test_convert_tools_identity(self, transport): tools = [{"type": "function", "function": {"name": "test", "parameters": {}}}] assert transport.convert_tools(tools) is tools diff --git a/tests/agent/transports/test_codex_transport.py b/tests/agent/transports/test_codex_transport.py index ccbf0963673..2b15fdf6096 100644 --- a/tests/agent/transports/test_codex_transport.py +++ b/tests/agent/transports/test_codex_transport.py @@ -75,6 +75,16 @@ class TestCodexBuildKwargs: ) assert kw.get("reasoning", {}).get("effort") == "high" + @pytest.mark.parametrize("effort, wire_effort", [("max", "max"), ("ultra", "max")]) + def test_extended_reasoning_efforts_use_api_wire_value(self, transport, effort, wire_effort): + kw = transport.build_kwargs( + model="gpt-5.6-sol", + messages=[{"role": "user", "content": "Hi"}], + tools=[], + reasoning_config={"enabled": True, "effort": effort}, + ) + assert kw.get("reasoning", {}).get("effort") == wire_effort + def test_reasoning_disabled(self, transport): messages = [{"role": "user", "content": "Hi"}] kw = transport.build_kwargs( @@ -401,6 +411,17 @@ class TestCodexBuildKwargs: # full history. assert "reasoning.encrypted_content" in kw.get("include", []) + @pytest.mark.parametrize("effort", ["xhigh", "max", "ultra"]) + def test_xai_stronger_generic_efforts_clamp_to_high(self, transport, effort): + kw = transport.build_kwargs( + model="grok-4.3", + messages=[{"role": "user", "content": "Hi"}], + tools=[], + is_xai_responses=True, + reasoning_config={"enabled": True, "effort": effort}, + ) + assert kw.get("reasoning") == {"effort": "high"} + def test_xai_injects_native_web_search_when_client_web_search_present(self, transport): """xAI path swaps a client-side ``web_search`` function for xAI's native server-side ``web_search`` built-in so grok server-side search diff --git a/tests/cli/test_reasoning_command.py b/tests/cli/test_reasoning_command.py index 08185689e73..5bd0c4cc7c9 100644 --- a/tests/cli/test_reasoning_command.py +++ b/tests/cli/test_reasoning_command.py @@ -30,7 +30,7 @@ class TestParseReasoningConfig(unittest.TestCase): self.assertEqual(result, {"enabled": False}) def test_valid_levels(self): - for level in ("low", "medium", "high", "xhigh", "minimal"): + for level in ("low", "medium", "high", "xhigh", "max", "ultra", "minimal"): result = self._parse(level) self.assertIsNotNone(result) self.assertTrue(result.get("enabled")) @@ -41,7 +41,6 @@ class TestParseReasoningConfig(unittest.TestCase): self.assertIsNone(self._parse(" ")) def test_unknown_returns_none(self): - self.assertIsNone(self._parse("ultra")) self.assertIsNone(self._parse("turbo")) def test_case_insensitive(self): diff --git a/tests/gateway/test_reasoning_command.py b/tests/gateway/test_reasoning_command.py index 09600fb6f5a..f7ebb0b5399 100644 --- a/tests/gateway/test_reasoning_command.py +++ b/tests/gateway/test_reasoning_command.py @@ -148,6 +148,29 @@ class TestReasoningCommand: assert runner._reasoning_config == {"enabled": True, "effort": "high"} assert "session only" in result + @pytest.mark.asyncio + @pytest.mark.parametrize("effort", ["max", "ultra"]) + async def test_handle_reasoning_command_accepts_extended_efforts( + self, tmp_path, monkeypatch, effort + ): + hermes_home = tmp_path / "hermes" + hermes_home.mkdir() + (hermes_home / "config.yaml").write_text( + "agent:\n reasoning_effort: medium\n", encoding="utf-8" + ) + monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home) + + runner = _make_runner() + event = _make_event(f"/reasoning {effort}") + session_key = runner._session_key_for_source(event.source) + + await runner._handle_reasoning_command(event) + + assert runner._session_reasoning_overrides[session_key] == { + "enabled": True, + "effort": effort, + } + @pytest.mark.asyncio async def test_reasoning_global_clears_existing_session_override(self, tmp_path, monkeypatch): hermes_home = tmp_path / "hermes" diff --git a/tests/hermes_cli/test_commands.py b/tests/hermes_cli/test_commands.py index 969ff559366..1473b0a5669 100644 --- a/tests/hermes_cli/test_commands.py +++ b/tests/hermes_cli/test_commands.py @@ -75,13 +75,15 @@ class TestCommandRegistry: 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] == ( + assert reasoning.subcommands[:8] == ( "none", "minimal", "low", "medium", "high", "xhigh", + "max", + "ultra", ) def test_cli_only_and_gateway_only_are_mutually_exclusive(self): diff --git a/tests/plugins/model_providers/test_ollama_cloud_profile.py b/tests/plugins/model_providers/test_ollama_cloud_profile.py index de1e2be44da..15e798a2cd6 100644 --- a/tests/plugins/model_providers/test_ollama_cloud_profile.py +++ b/tests/plugins/model_providers/test_ollama_cloud_profile.py @@ -106,9 +106,9 @@ class TestOllamaCloudReasoningEffort: def test_unknown_effort_forwarded(self, ollama_cloud_profile): _, top_level = ollama_cloud_profile.build_api_kwargs_extras( - reasoning_config={"enabled": True, "effort": "ultra"}, + reasoning_config={"enabled": True, "effort": "future-tier"}, ) - assert top_level == {"reasoning_effort": "ultra"} + assert top_level == {"reasoning_effort": "future-tier"} class TestOllamaCloudFullKwargsIntegration: diff --git a/tests/providers/test_provider_profiles.py b/tests/providers/test_provider_profiles.py index ba803640728..bd450cb5600 100644 --- a/tests/providers/test_provider_profiles.py +++ b/tests/providers/test_provider_profiles.py @@ -314,12 +314,12 @@ class TestOpenRouterProfile: Covers the full real config range produced by ``hermes_constants.parse_reasoning_effort`` — - ``VALID_REASONING_EFFORTS = (minimal, low, medium, high, xhigh)``. + ``VALID_REASONING_EFFORTS`` (including max and ultra). """ p = get_provider_profile("openrouter") model = "anthropic/claude-fable-5" assert self._is_mandatory(model) # fixture really is mandatory - for effort in ("minimal", "low", "medium", "high", "xhigh"): + for effort in ("minimal", "low", "medium", "high", "xhigh", "max", "ultra"): eb, tl = p.build_api_kwargs_extras( reasoning_config={"enabled": True, "effort": effort}, supports_reasoning=True, @@ -342,14 +342,12 @@ class TestOpenRouterProfile: def test_mandatory_anthropic_verbosity_is_value_agnostic_passthrough(self): """The mapping passes the effort value through verbatim — it must NOT - clamp or whitelist. ``xhigh`` is a real config value; ``max`` is not - producible by ``parse_reasoning_effort`` today but OpenRouter accepts it - for Claude (live-proven in #43432), so a forward value must survive + clamp or whitelist. Extended values must survive rather than be silently dropped. The OpenAI SDK type only literals ``low|medium|high`` but it's a TypedDict (no runtime validation), so the extended scale reaches the wire untouched.""" p = get_provider_profile("openrouter") - for effort in ("xhigh", "max"): + for effort in ("xhigh", "max", "ultra"): _, tl = p.build_api_kwargs_extras( reasoning_config={"enabled": True, "effort": effort}, supports_reasoning=True, diff --git a/tests/test_hermes_constants.py b/tests/test_hermes_constants.py index e4b064ed947..0eac5af5960 100644 --- a/tests/test_hermes_constants.py +++ b/tests/test_hermes_constants.py @@ -483,10 +483,10 @@ class TestParseReasoningEffort: """Guard against silently dropping a documented level. The docstring promises "minimal", "low", "medium", "high", "xhigh", - "max". If someone removes one from VALID_REASONING_EFFORTS without + "max", "ultra". If someone removes one from VALID_REASONING_EFFORTS without updating the docstring, this test will fail and force the call out. """ - documented = {"minimal", "low", "medium", "high", "xhigh", "max"} + documented = {"minimal", "low", "medium", "high", "xhigh", "max", "ultra"} assert documented.issubset(set(VALID_REASONING_EFFORTS)) diff --git a/web/src/lib/reasoning-effort.test.ts b/web/src/lib/reasoning-effort.test.ts index 3ade0034724..9c2d0b139ae 100644 --- a/web/src/lib/reasoning-effort.test.ts +++ b/web/src/lib/reasoning-effort.test.ts @@ -14,7 +14,7 @@ describe("normalizeEffort", () => { }); it("passes through every valid effort level", () => { - for (const level of ["none", "minimal", "low", "medium", "high", "xhigh"]) { + for (const level of ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]) { expect(normalizeEffort(level)).toBe(level); } }); @@ -26,7 +26,6 @@ describe("normalizeEffort", () => { it("falls back to medium for unknown values", () => { expect(normalizeEffort("turbo")).toBe("medium"); - expect(normalizeEffort("max")).toBe("medium"); // 'max' is a label, not a value expect(normalizeEffort(42)).toBe("medium"); }); }); @@ -41,7 +40,7 @@ describe("EFFORT_OPTIONS", () => { it("covers the real reasoning levels plus thinking-off", () => { // Invariant against hermes_constants.VALID_REASONING_EFFORTS + 'none'. const values = new Set(EFFORT_OPTIONS.map((o) => o.value)); - for (const level of ["none", "minimal", "low", "medium", "high", "xhigh"]) { + for (const level of ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]) { expect(values.has(level)).toBe(true); } }); diff --git a/web/src/lib/reasoning-effort.ts b/web/src/lib/reasoning-effort.ts index 1e8313e0489..2d5fecd3120 100644 --- a/web/src/lib/reasoning-effort.ts +++ b/web/src/lib/reasoning-effort.ts @@ -20,7 +20,9 @@ export const EFFORT_OPTIONS: ReadonlyArray = [ { value: "low", label: "Low" }, { value: "medium", label: "Medium" }, { value: "high", label: "High" }, - { value: "xhigh", label: "Max" }, + { value: "xhigh", label: "Extra High" }, + { value: "max", label: "Max" }, + { value: "ultra", label: "Ultra" }, ]; export const VALID_EFFORTS: ReadonlySet = new Set( diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index d9a4353e23e..e65399a67d8 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -1276,7 +1276,7 @@ Control how much "thinking" the model does before responding: ```yaml agent: - reasoning_effort: "" # empty = medium (default). Options: none, minimal, low, medium, high, xhigh (max) + reasoning_effort: "" # empty = medium. Options: none, minimal, low, medium, high, xhigh, max, ultra ``` When unset (default), reasoning effort defaults to "medium" — a balanced level that works well for most tasks. Setting a value overrides it — higher reasoning effort gives better results on complex tasks at the cost of more tokens and latency. @@ -1285,10 +1285,9 @@ When unset (default), reasoning effort defaults to "medium" — a balanced level These models use *adaptive* thinking and don't accept the usual `reasoning.effort` field — OpenRouter ignores it for them. Hermes transparently routes your `reasoning_effort` to OpenRouter's `verbosity` parameter instead (which maps to -Anthropic's `output_config.effort`), so the same `low`/`medium`/`high`/`xhigh` -knob keeps working — no extra configuration needed. `none` (or unset) leaves the -model on its own adaptive default. (`max` is accepted on the wire but is not a -selectable `reasoning_effort` value; `xhigh` is the configurable ceiling.) The +Anthropic's `output_config.effort`), so the same effort knob keeps working with +the levels supported by the selected model. `none` (or unset) leaves the model +on its own adaptive default. The native Anthropic provider already controls effort directly and is unaffected. ::: diff --git a/website/docs/user-guide/features/batch-processing.md b/website/docs/user-guide/features/batch-processing.md index 1abbac977bd..87bbf03af16 100644 --- a/website/docs/user-guide/features/batch-processing.md +++ b/website/docs/user-guide/features/batch-processing.md @@ -83,7 +83,7 @@ Entries can optionally include: | Parameter | Description | |-----------|-------------| -| `--reasoning_effort` | Effort level: `none`, `minimal`, `low`, `medium`, `high`, `xhigh` | +| `--reasoning_effort` | Reasoning effort: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `ultra` | | `--reasoning_disabled` | Completely disable reasoning/thinking tokens | ### Advanced Options diff --git a/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md b/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md index 7c9eec273f5..2dde2ad9d12 100644 --- a/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md +++ b/website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md @@ -286,7 +286,7 @@ The registry of record is `hermes_cli/commands.py` — every consumer /config Show config (CLI) /model [name] Show or change model /personality [name] Set personality -/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|show|hide) +/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|max|ultra|show|hide) /verbose Cycle: off → new → all → verbose /voice [on|off|tts] Voice mode /yolo Toggle approval bypass diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md index c2474028849..2d290c84652 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md @@ -1044,7 +1044,7 @@ auxiliary: ```yaml agent: - reasoning_effort: "" # 空 = 中等(默认)。选项:none、minimal、low、medium、high、xhigh(最大) + reasoning_effort: "" # 空 = 中等。选项:none、minimal、low、medium、high、xhigh、max、ultra ``` 未设置时(默认),推理努力程度默认为"medium" —— 适合大多数任务的平衡级别。设置值会覆盖它 —— 更高的推理努力程度在复杂任务上提供更好的结果,但代价是更多 token 和延迟。 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/batch-processing.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/batch-processing.md index 0ecc8112b67..0c62b94ba40 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/batch-processing.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/batch-processing.md @@ -83,7 +83,7 @@ python batch_runner.py --list_distributions | 参数 | 说明 | |-----------|-------------| -| `--reasoning_effort` | 推理力度:`none`、`minimal`、`low`、`medium`、`high`、`xhigh` | +| `--reasoning_effort` | 推理力度:`none`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`、`ultra` | | `--reasoning_disabled` | 完全禁用推理/思考 token | ### 高级选项 diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md index c1d8a9436a7..c87ef643a7f 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md @@ -275,7 +275,7 @@ hermes uninstall Uninstall Hermes /config Show config (CLI) /model [name] Show or change model /personality [name] Set personality -/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|show|hide) +/reasoning [level] Set reasoning (none|minimal|low|medium|high|xhigh|max|ultra|show|hide) /verbose Cycle: off → new → all → verbose /voice [on|off|tts] Voice mode /yolo Toggle approval bypass