From 776d48380e927a8dd3a410c0028b5f62c6a22cbf Mon Sep 17 00:00:00 2001 From: fdc310 <2213070223@qq.com> Date: Wed, 24 Jun 2026 14:14:14 +0800 Subject: [PATCH] feat(telegram): enhance message handling with group stream deletion and form placeholder detection --- src/langbot/pkg/platform/sources/telegram.py | 85 +++++++++++++++----- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/src/langbot/pkg/platform/sources/telegram.py b/src/langbot/pkg/platform/sources/telegram.py index 76631f18e..135f0127c 100644 --- a/src/langbot/pkg/platform/sources/telegram.py +++ b/src/langbot/pkg/platform/sources/telegram.py @@ -429,28 +429,34 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): args['parse_mode'] = 'MarkdownV2' return args + async def _delete_group_stream_message(self, chat_mode: str, chat_id: int, stream_id: int | None): + if chat_mode != 'group' or stream_id is None: + return + try: + await self.bot.delete_message(chat_id=chat_id, message_id=stream_id) + except telegram.error.TelegramError: + pass + + @staticmethod + def _is_form_placeholder_chunk(text: str) -> bool: + """Return True for invisible placeholder chunks used to carry forms.""" + + if not text: + return True + + cleaned = text.replace('\u200b', '').replace('\u200c', '').replace('\u200d', '').replace('\ufeff', '').strip() + return cleaned == '' + async def create_message_card(self, message_id, event): assert isinstance(event.source_platform_object, Update) update = event.source_platform_object chat_id = update.effective_chat.id - chat_type = update.effective_chat.type effective_message = update.effective_message message_thread_id = getattr(effective_message, 'message_thread_id', None) if effective_message else None - if chat_type == 'private': - import time as _time - - draft_id = int(_time.time() * 1000) - self.msg_stream_id[message_id] = ('private', draft_id) - args = self._build_message_args(chat_id, 'Thinking...', message_thread_id, draft_id=draft_id) - try: - await self.bot.send_message_draft(**args) - except (telegram.error.RetryAfter, telegram.error.BadRequest): - pass - else: - args = self._build_message_args(chat_id, 'Thinking...', message_thread_id) - send_msg = await self.bot.send_message(**args) - self.msg_stream_id[message_id] = ('group', send_msg.message_id) + args = self._build_message_args(chat_id, 'Thinking...', message_thread_id) + send_msg = await self.bot.send_message(**args) + self.msg_stream_id[message_id] = ('message', send_msg.message_id, False) return True @@ -473,7 +479,9 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): if message_id not in self.msg_stream_id: return - chat_mode, stream_id = self.msg_stream_id[message_id] + stream_state = self.msg_stream_id[message_id] + chat_mode, stream_id = stream_state[:2] + has_visible_content = len(stream_state) > 2 and stream_state[2] components = await TelegramMessageConverter.yiri2target(message, self.bot) if not components or components[0]['type'] != 'text': @@ -485,8 +493,17 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): form_data = getattr(bot_message, '_form_data', None) if form_data and is_final: + if not has_visible_content: + await self._send_form_action_buttons(message_source, form_data, edit_message_id=stream_id) + else: + await self._send_form_action_buttons(message_source, form_data) self.msg_stream_id.pop(message_id, None) - await self._send_form_action_buttons(message_source, form_data) + return + + if self._is_form_placeholder_chunk(content): + if is_final and bot_message.tool_calls is None and not has_visible_content: + await self._delete_group_stream_message(chat_mode, chat_id, stream_id) + self.msg_stream_id.pop(message_id, None) return if chat_mode == 'private': @@ -504,6 +521,7 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): pass else: pass # Ignore other draft errors (cosmetic) + self.msg_stream_id[message_id] = (chat_mode, stream_id, True) if is_final and bot_message.tool_calls is None: # Finalise: send the real message, discard the draft args = self._build_message_args(chat_id, content, message_thread_id) @@ -518,7 +536,22 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): self.msg_stream_id.pop(message_id) else: # Streaming via edit_message_text (persistent message) - if (msg_seq - 1) % 8 == 0 or is_final: + if stream_id is None: + args = self._build_message_args(chat_id, content, message_thread_id) + try: + send_msg = await self.bot.send_message(**args) + except telegram.error.BadRequest as exc: + if 'Message_too_long' in str(exc): + args['text'] = self._process_markdown(content[:4000] + '\n\n鈥?(truncated)') + send_msg = await self.bot.send_message(**args) + else: + raise + self.msg_stream_id[message_id] = (chat_mode, send_msg.message_id, True) + if is_final and bot_message.tool_calls is None: + self.msg_stream_id.pop(message_id, None) + return + + if not has_visible_content or (msg_seq - 1) % 8 == 0 or is_final: args = { 'message_id': stream_id, 'chat_id': chat_id, @@ -534,6 +567,7 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): await self.bot.edit_message_text(**args) else: raise + self.msg_stream_id[message_id] = (chat_mode, stream_id, True) if is_final and bot_message.tool_calls is None: self.msg_stream_id.pop(message_id) @@ -542,6 +576,7 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): self, message_source: platform_events.MessageEvent, form_data: dict, + edit_message_id: int | None = None, ): """Send inline keyboard buttons for Dify human_input_required form actions.""" actions = form_data.get('actions', []) @@ -604,6 +639,20 @@ class TelegramAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): if message_thread_id: args['message_thread_id'] = message_thread_id + if edit_message_id is not None: + edit_args = { + 'chat_id': chat_id, + 'message_id': edit_message_id, + 'text': args['text'], + } + if 'reply_markup' in args: + edit_args['reply_markup'] = args['reply_markup'] + try: + await self.bot.edit_message_text(**edit_args) + return + except telegram.error.TelegramError: + await self._delete_group_stream_message('group', chat_id, edit_message_id) + await self.bot.send_message(**args) def get_launcher_id(self, event: platform_events.MessageEvent) -> str | None: