fix(telegram): fall back buttons to inline text when keyboard disabled
Buttons are semantic options, not a separate channel protocol: a user who taps "Yes" and a user who types "yes" arrive at the agent as the same string. Dropping ``msg.buttons`` when ``inline_keyboards=False`` was the worst of both worlds — the agent got told "Message sent with N button(s)" while the user saw a question with no options. Splice the labels into the message text instead. The LLM produces the same ``message(buttons=...)`` call regardless of channel; the channel layer picks the richest rendering it can afford — native keyboard when enabled, bracketed inline text otherwise. Layout is preserved (one row per line). Other channels can adopt the same helper incrementally. Locks: canonical ``_buttons_as_text`` format, flag-off send-path splices labels, flag-on send-path keeps content clean and rides ``reply_markup``. Made-with: Cursor
This commit is contained in:
@@ -520,8 +520,13 @@ class TelegramChannel(BaseChannel):
|
||||
# Send text content
|
||||
if msg.content and msg.content != "[empty message]":
|
||||
render_as_blockquote = bool(msg.metadata.get("_tool_hint"))
|
||||
chunks = split_message(msg.content, TELEGRAM_MAX_MESSAGE_LEN)
|
||||
reply_markup = self._build_keyboard(msg.buttons) if getattr(msg, 'buttons', None) else None
|
||||
buttons = getattr(msg, "buttons", None) or []
|
||||
reply_markup = self._build_keyboard(buttons) if buttons else None
|
||||
text = msg.content
|
||||
# Fallback: no native keyboard → splice labels into the message so the choices survive.
|
||||
if buttons and reply_markup is None:
|
||||
text = f"{text}\n\n{self._buttons_as_text(buttons)}"
|
||||
chunks = split_message(text, TELEGRAM_MAX_MESSAGE_LEN)
|
||||
for i, chunk in enumerate(chunks):
|
||||
is_last = (i == len(chunks) - 1)
|
||||
await self._send_text(
|
||||
@@ -1205,6 +1210,11 @@ class TelegramChannel(BaseChannel):
|
||||
]
|
||||
return InlineKeyboardMarkup(keyboard)
|
||||
|
||||
@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.
|
||||
return "\n".join(" ".join(f"[{label}]" for label in row) for row in buttons if row)
|
||||
|
||||
async def _on_callback_query(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Handle inline keyboard button clicks (callback queries)."""
|
||||
if not update.callback_query or not update.effective_user:
|
||||
|
||||
Reference in New Issue
Block a user