From 6cc07b6cd0344e63340aa003a5e90a5bdefe14c0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 23 Jun 2026 10:44:02 -0700 Subject: [PATCH] feat(discord): render reasoning as -# subtext via display.reasoning_style (#51168) Adds a per-platform display.reasoning_style setting (code | blockquote | subtext) controlling how the show_reasoning summary renders on the gateway. Discord defaults to "subtext" (-# small grey metadata text); every other platform keeps the fenced code block. Resolves through the existing display.platforms..reasoning_style override chain. --- gateway/display_config.py | 14 ++++++++- gateway/run.py | 26 +++++++++++++++- hermes_cli/config.py | 6 ++++ tests/gateway/test_display_config.py | 45 ++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/gateway/display_config.py b/gateway/display_config.py index 58226ed48fe..0d8b5699516 100644 --- a/gateway/display_config.py +++ b/gateway/display_config.py @@ -34,6 +34,12 @@ _GLOBAL_DEFAULTS: dict[str, Any] = { "tool_progress": "all", "tool_progress_grouping": "accumulate", # "accumulate" = edit one bubble; "separate" = one msg per tool "show_reasoning": False, + # How a reasoning/thinking summary is rendered when show_reasoning is on. + # "code" -> 💭 **Reasoning:** + fenced code block (legacy default) + # "blockquote"-> each line prefixed with "> " + # "subtext" -> each line prefixed with "-# " (Discord small grey subtext) + # Discord defaults to "subtext"; everywhere else defaults to "code". + "reasoning_style": "code", "tool_preview_length": 0, "streaming": None, # None = follow top-level streaming config # Gateway-only assistant/status chatter controls. These default on for @@ -111,7 +117,10 @@ _PLATFORM_DEFAULTS: dict[str, dict[str, Any]] = { "tool_progress": "off", "busy_ack_detail": False, }, - "discord": _TIER_HIGH, + # Discord has a native "subtext" primitive (-# small grey text) that reads + # as metadata rather than content, so reasoning summaries default to it + # here instead of the fenced code block used elsewhere. + "discord": {**_TIER_HIGH, "reasoning_style": "subtext"}, # Tier 2 — edit support, often customer/workspace channels # Slack: tool_progress off by default — Bolt posts cannot be edited like CLI; @@ -242,6 +251,9 @@ def _normalise(setting: str, value: Any) -> Any: if setting == "tool_progress_grouping": val = str(value).lower() return val if val in ("accumulate", "separate") else "accumulate" + if setting == "reasoning_style": + val = str(value).lower() + return val if val in ("code", "blockquote", "subtext") else "code" if setting == "tool_preview_length": try: return int(value) diff --git a/gateway/run.py b/gateway/run.py index 09b9e1c88f9..980f2a4e993 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -9733,7 +9733,31 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew display_reasoning += f"\n_... ({len(lines) - 15} more lines)_" else: display_reasoning = last_reasoning.strip() - response = f"💭 **Reasoning:**\n```\n{display_reasoning}\n```\n\n{response}" + # Render style is per-platform: Discord defaults to "-# " + # subtext (native small grey metadata text); other + # platforms keep the fenced code block. + try: + from gateway.display_config import resolve_display_setting + _reasoning_style = resolve_display_setting( + _load_gateway_config(), + _platform_config_key(source.platform), + "reasoning_style", + "code", + ) + except Exception: + _reasoning_style = "code" + if _reasoning_style == "subtext": + _quoted = "\n".join( + f"-# {ln}" if ln else "-#" for ln in display_reasoning.splitlines() + ) + response = f"-# 💭 Reasoning\n{_quoted}\n\n{response}" + elif _reasoning_style == "blockquote": + _quoted = "\n".join( + f"> {ln}" if ln else ">" for ln in display_reasoning.splitlines() + ) + response = f"> 💭 **Reasoning:**\n{_quoted}\n\n{response}" + else: + response = f"💭 **Reasoning:**\n```\n{display_reasoning}\n```\n\n{response}" # Runtime-metadata footer — only on the FINAL message of the turn. # Off by default (display.runtime_footer.enabled=false). When diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 34923375984..ca0dbfd2a6b 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -1667,6 +1667,12 @@ DEFAULT_CONFIG = { # applies where tool_progress is already enabled. Per-platform override # via display.platforms..tool_progress_grouping. "tool_progress_grouping": "accumulate", + # How a reasoning/thinking summary renders when show_reasoning is on. + # "code" (default) = 💭 fenced code block; "blockquote" = "> " lines; + # "subtext" = "-# " lines (Discord small grey metadata text). Discord + # defaults to "subtext"; override per-platform via + # display.platforms..reasoning_style. + "reasoning_style": "code", # Auto-delete system-notice replies (e.g. "✨ New session started!", # "♻ Restarting gateway…", "⚡ Stopped…") after N seconds on platforms # that support message deletion (currently Telegram; other platforms diff --git a/tests/gateway/test_display_config.py b/tests/gateway/test_display_config.py index 06787407555..81bbc912fab 100644 --- a/tests/gateway/test_display_config.py +++ b/tests/gateway/test_display_config.py @@ -510,3 +510,48 @@ class TestToolProgressGrouping: resolve_display_setting(config, "telegram", "tool_progress_grouping") == "separate" ) + + +class TestReasoningStyle: + """Per-platform reasoning render style (code | blockquote | subtext).""" + + def test_discord_defaults_to_subtext(self): + from gateway.display_config import resolve_display_setting + + assert resolve_display_setting({}, "discord", "reasoning_style") == "subtext" + + def test_other_platforms_default_to_code(self): + from gateway.display_config import resolve_display_setting + + for plat in ("telegram", "slack", "matrix", "api_server"): + assert ( + resolve_display_setting({}, plat, "reasoning_style") == "code" + ), plat + + def test_platform_override_wins(self): + from gateway.display_config import resolve_display_setting + + config = {"display": {"platforms": {"discord": {"reasoning_style": "blockquote"}}}} + assert ( + resolve_display_setting(config, "discord", "reasoning_style") == "blockquote" + ) + + def test_global_override(self): + from gateway.display_config import resolve_display_setting + + config = {"display": {"reasoning_style": "subtext"}} + assert ( + resolve_display_setting(config, "telegram", "reasoning_style") == "subtext" + ) + + def test_invalid_value_falls_back_to_code(self): + from gateway.display_config import resolve_display_setting + + config = {"display": {"reasoning_style": "bogus"}} + assert resolve_display_setting(config, "telegram", "reasoning_style") == "code" + + def test_case_insensitive(self): + from gateway.display_config import resolve_display_setting + + config = {"display": {"reasoning_style": "SUBTEXT"}} + assert resolve_display_setting(config, "telegram", "reasoning_style") == "subtext"