From f19b5715647e87727cdd989d1a1023376529ba5d Mon Sep 17 00:00:00 2001 From: asdlem Date: Mon, 18 May 2026 15:57:02 +0800 Subject: [PATCH 1/2] fix(telegram): render full clarify choice text in message body, use short button labels When Telegram clarify prompts offer long choices, mobile clients truncate the inline button labels, making options unreadable. Previously only the question was shown in the message body with truncated choice text in button labels. Fix: append the full numbered option list to the message body so users can read complete choice text on any client. Buttons now use short numeric labels (1, 2, ...) to avoid Telegram truncation. The 'Other (type answer)' button is unchanged. Long choice labels are now rendered in full (not truncated to 57 chars + '...') since they appear in the body instead of button labels. Closes: #27497 --- gateway/platforms/telegram.py | 20 +++++++++++++------ .../gateway/test_telegram_clarify_buttons.py | 19 +++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index d893b8115cf48..9bc04c43653bb 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -2262,6 +2262,17 @@ async def send_clarify( text = f"❓ {_html.escape(question)}" thread_id = self._metadata_thread_id(metadata) + if choices: + # Render full option text in the message body so mobile + # users can read long choices that would be truncated in + # inline button labels. Buttons keep short numeric labels + # (1, 2, …, Other) to avoid Telegram truncation. + option_lines = "\n".join( + f"{i + 1}. {_html.escape(str(c))}" + for i, c in enumerate(choices) + ) + text += f"\n\n{option_lines}" + kwargs: Dict[str, Any] = { "chat_id": int(chat_id), "text": text, @@ -2271,15 +2282,12 @@ async def send_clarify( if choices: # Telegram caps callback_data at 64 bytes; keep "cl::" - # short. Button label is also capped (~64 chars in practice). + # short. rows = [] - for idx, choice in enumerate(choices): - label = str(choice) - if len(label) > 60: - label = label[:57] + "..." + for idx in range(len(choices)): rows.append([ InlineKeyboardButton( - f"{idx + 1}. {label}", + str(idx + 1), callback_data=f"cl:{clarify_id}:{idx}", ) ]) diff --git a/tests/gateway/test_telegram_clarify_buttons.py b/tests/gateway/test_telegram_clarify_buttons.py index b9e7bd5130f8b..56c0f9e60c4eb 100644 --- a/tests/gateway/test_telegram_clarify_buttons.py +++ b/tests/gateway/test_telegram_clarify_buttons.py @@ -100,6 +100,10 @@ async def test_multi_choice_renders_buttons_and_other(self): kwargs = adapter._bot.send_message.call_args[1] assert kwargs["chat_id"] == 12345 assert "Which option?" in kwargs["text"] + # Full option text rendered in the message body (not just buttons) + assert "1. alpha" in kwargs["text"] + assert "2. beta" in kwargs["text"] + assert "3. gamma" in kwargs["text"] # InlineKeyboardMarkup with N+1 buttons (3 choices + Other) markup = kwargs["reply_markup"] assert markup is not None @@ -144,13 +148,15 @@ async def test_not_connected(self): assert result.success is False @pytest.mark.asyncio - async def test_truncates_long_choice_label(self): + async def test_long_choice_rendered_in_body_not_truncated(self): + """Long choice text appears in full in the message body; + button labels stay short numeric (1, 2, …).""" adapter = _make_adapter() mock_msg = MagicMock() mock_msg.message_id = 102 adapter._bot.send_message = AsyncMock(return_value=mock_msg) - long_choice = "x" * 200 # > 60 char cap + long_choice = "x" * 200 result = await adapter.send_clarify( chat_id="12345", question="?", @@ -159,9 +165,12 @@ async def test_truncates_long_choice_label(self): session_key="sk4", ) assert result.success is True - # The truncation logic replaces with "..." past 57 chars; we don't - # inspect the mock's button labels directly (auto-MagicMock), but - # we can verify the call didn't raise on absurdly long input. + kwargs = adapter._bot.send_message.call_args[1] + # The full long choice text appears in the message body + assert long_choice in kwargs["text"] + # The button label should be short ("1"), not the long choice + # (we can't inspect mock button labels directly, but the send + # succeeded — old truncation code could raise on edge cases) @pytest.mark.asyncio async def test_html_escapes_question(self): From c383da35782c58e9d7afa4c9e55f1c9e9e549f54 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 18 May 2026 21:48:55 -0700 Subject: [PATCH 2/2] chore(release): map @asdlem for PR #27852 salvage --- scripts/release.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/release.py b/scripts/release.py index ec9dca3ac3818..7dfa2a24a1ec7 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -1205,6 +1205,7 @@ "abc401011721@gmail.com": "samggggflynn", "yannsunn@users.noreply.github.com": "yannsunn", # PR #28064 salvage (xai proxy upstream) "yannsunn1116@gmail.com": "yannsunn", + "asdlem@users.noreply.github.com": "asdlem", # PR #27852 salvage (clarify full text in body) }