From 841b4df09def08fefa55a82475b649b603871029 Mon Sep 17 00:00:00 2001 From: Haoxuan Jiang Date: Mon, 19 Oct 2026 16:26:16 +0800 Subject: [PATCH] fix(lark): preserve incoming rich-text code blocks (#2626) --- .../adapters/lark/message_converter.py | 2 +- src/langbot/pkg/platform/sources/lark.py | 2 +- .../platform/test_lark_inbound_code_blocks.py | 78 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests/platform/test_lark_inbound_code_blocks.py diff --git a/src/langbot/pkg/platform/adapters/lark/message_converter.py b/src/langbot/pkg/platform/adapters/lark/message_converter.py index 510246cf8..40db36bde 100644 --- a/src/langbot/pkg/platform/adapters/lark/message_converter.py +++ b/src/langbot/pkg/platform/adapters/lark/message_converter.py @@ -184,7 +184,7 @@ class LarkMessageConverter(abstract_platform_adapter.AbstractMessageConverter): normalized = LarkMessageConverter._normalize_inbound_content(message, message_content) for ele in normalized: tag = ele.get('tag') - if tag in {'text', 'md'}: + if tag in {'text', 'md', 'code_block'}: text = ele.get('text') or '' if text: components.append(platform_message.Plain(text=text)) diff --git a/src/langbot/pkg/platform/sources/lark.py b/src/langbot/pkg/platform/sources/lark.py index c4bd5c8a9..4dc600c8f 100644 --- a/src/langbot/pkg/platform/sources/lark.py +++ b/src/langbot/pkg/platform/sources/lark.py @@ -726,7 +726,7 @@ class LarkMessageConverter(abstract_platform_adapter.AbstractMessageConverter): ] for ele in message_content['content']: - if ele['tag'] == 'text': + if ele['tag'] in {'text', 'code_block'}: lb_msg_list.append(platform_message.Plain(text=ele['text'])) elif ele['tag'] == 'at': lb_msg_list.append(platform_message.At(target=ele['user_name'])) diff --git a/tests/unit_tests/platform/test_lark_inbound_code_blocks.py b/tests/unit_tests/platform/test_lark_inbound_code_blocks.py new file mode 100644 index 000000000..965abce56 --- /dev/null +++ b/tests/unit_tests/platform/test_lark_inbound_code_blocks.py @@ -0,0 +1,78 @@ +"""Lark rich-text code blocks must reach the bot as text instead of being dropped.""" + +import json +from types import SimpleNamespace +from unittest.mock import MagicMock + +import pytest + +from langbot.pkg.platform.adapters.lark.message_converter import LarkMessageConverter +from langbot.pkg.platform.sources.lark import LarkMessageConverter as LegacyLarkMessageConverter +from langbot_plugin.api.entities.builtin.platform import message as platform_message + + +@pytest.fixture(params=[LegacyLarkMessageConverter, LarkMessageConverter], ids=['legacy', 'omni']) +def converter(request): + return request.param + + +def post_message(content): + return SimpleNamespace( + message_id='post-msg', + message_type='post', + create_time='1714000000000', + content=json.dumps({'title': '', 'content': content}), + mentions=[], + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'code_block', + [ + {'tag': 'code_block', 'language': 'GO', 'text': 'func main() int64 {\n return 0\n}'}, + {'tag': 'code_block', 'language': 'PYTHON', 'text': 'def hello():\n print("你好")\n'}, + {'tag': 'code_block', 'text': ' indented\n\nlast line'}, + ], + ids=['official-go-example', 'python-unicode', 'no-language'], +) +async def test_inbound_post_preserves_code_block_text(converter, code_block): + api_client = MagicMock() + + chain = await converter.target2yiri(post_message([[code_block]]), api_client) + + assert len(chain) == 2 + assert isinstance(chain[0], platform_message.Source) + assert chain[0].id == 'post-msg' + assert isinstance(chain[1], platform_message.Plain) + assert chain[1].text == code_block['text'] + assert api_client.mock_calls == [] + + +@pytest.mark.asyncio +async def test_inbound_post_preserves_code_between_text_and_mentions(converter): + api_client = MagicMock() + chain = await converter.target2yiri( + post_message( + [ + [{'tag': 'at', 'user_id': 'ou_bot', 'user_name': 'Bot'}, {'tag': 'text', 'text': 'Explain: '}], + [{'tag': 'code_block', 'language': 'PYTHON', 'text': 'print(1 + 2)'}], + [{'tag': 'text', 'text': ' What is the result?'}], + ] + ), + api_client, + ) + + assert [type(component) for component in chain] == [ + platform_message.Source, + platform_message.At, + platform_message.Plain, + platform_message.Plain, + platform_message.Plain, + ] + assert [component.text for component in chain if isinstance(component, platform_message.Plain)] == [ + 'Explain: ', + 'print(1 + 2)', + ' What is the result?', + ] + assert api_client.mock_calls == []