Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions gateway/platforms/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -2271,15 +2282,12 @@ async def send_clarify(

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}",
)
])
Expand Down
19 changes: 14 additions & 5 deletions tests/gateway/test_telegram_clarify_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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="?",
Expand All @@ -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):
Expand Down