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
This commit is contained in:
asdlem 2026-05-18 15:57:02 +08:00 committed by Teknium
parent 19128108ac
commit 6fb57bc9cf
2 changed files with 28 additions and 11 deletions

View file

@ -2262,6 +2262,17 @@ class TelegramAdapter(BasePlatformAdapter):
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 @@ class TelegramAdapter(BasePlatformAdapter):
if choices:
# Telegram caps callback_data at 64 bytes; keep "cl:<id>:<idx>"
# 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}",
)
])

View file

@ -100,6 +100,10 @@ class TestTelegramSendClarify:
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 @@ class TestTelegramSendClarify:
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 @@ class TestTelegramSendClarify:
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):