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.<platform>.reasoning_style override chain.
This commit is contained in:
Teknium 2026-06-23 10:44:02 -07:00 committed by GitHub
parent f32be4439c
commit 6cc07b6cd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 2 deletions

View file

@ -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)

View file

@ -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

View file

@ -1667,6 +1667,12 @@ DEFAULT_CONFIG = {
# applies where tool_progress is already enabled. Per-platform override
# via display.platforms.<platform>.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.<platform>.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

View file

@ -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"