fix(telegram): keep callback_data under Telegram's 64-byte cap

``InlineKeyboardButton(label, callback_data=label)`` fails Telegram's
API when the label exceeds 64 bytes UTF-8. An LLM-generated long
option (realistic in multilingual flows) used to 400 the ``send_message``
call silently — user got nothing, agent heard a successful retry-then-drop.

Decouple display from wire: button text keeps the full label, callback_data
gets truncated at a UTF-8 char boundary. Tap echoes the prefix back as the
user message; the LLM understands a prefix of its own option just fine,
and the display the user saw was always the full string.

Locks: helper boundary behavior (ASCII, CJK, short labels pass through)
and end-to-end ``_build_keyboard`` integration with an over-cap label.

Made-with: Cursor
This commit is contained in:
Xubin Ren
2026-04-23 13:26:06 +08:00
committed by Xubin Ren
parent 6bc2983ab1
commit 06503cd0fc
2 changed files with 44 additions and 1 deletions
+9 -1
View File
@@ -1205,11 +1205,19 @@ class TelegramChannel(BaseChannel):
if not buttons or not self.config.inline_keyboards:
return None
keyboard = [
[InlineKeyboardButton(label, callback_data=label) for label in row]
[InlineKeyboardButton(label, callback_data=self._safe_callback_data(label)) for label in row]
for row in buttons
]
return InlineKeyboardMarkup(keyboard)
@staticmethod
def _safe_callback_data(label: str) -> str:
# Telegram caps callback_data at 64 bytes UTF-8; truncate at a char boundary so the keyboard still sends.
encoded = label.encode("utf-8")
if len(encoded) <= 64:
return label
return encoded[:64].decode("utf-8", errors="ignore")
@staticmethod
def _buttons_as_text(buttons: list[list[str]]) -> str:
# Buttons are semantic options; when we can't render a keyboard, the user still needs to see them.