Skip to content
Merged
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
9 changes: 9 additions & 0 deletions src/langbot_plugin/api/entities/builtin/platform/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ def model_validate(cls, obj):
item[field_name] = MessageChain.model_validate(
field_value
)
# Special handling for Forward: recursively deserialize
# message_chain inside each ForwardMessageNode
if component_type == "Forward" and "node_list" in item:
for node_data in item["node_list"]:
if isinstance(node_data, dict) and "message_chain" in node_data:
mc = node_data["message_chain"]
if isinstance(mc, list):
node_data["message_chain"] = MessageChain.model_validate(mc)

# Special processing of the time field of the Source class
if component_type == "Source" and "time" in item:
item["time"] = datetime.fromtimestamp(item["time"])
Expand Down
81 changes: 81 additions & 0 deletions tests/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,87 @@ def test_message_chain_with_forward():
assert chain[0].display.title == "Test Forward"


def test_forward_message_chain_deserialization():
"""测试 Forward 消息的 node_list 中 message_chain 的反序列化"""
# Simulate raw dict data as it would come over the wire (e.g. from plugin runtime)
raw = [
{
"type": "Forward",
"display": {
"title": "Chat history",
"brief": "[Chat history]",
"source": "Chat history",
"preview": [],
"summary": "View 2 forwarded messages",
},
"node_list": [
{
"sender_id": "111",
"sender_name": "Alice",
"message_chain": [
{"type": "Plain", "text": "Hello from Alice"},
{"type": "Image", "url": "http://example.com/a.png"},
],
"message_id": 1,
},
{
"sender_id": "222",
"sender_name": "Bob",
"message_chain": [
{"type": "Plain", "text": "Hello from Bob"},
],
"message_id": 2,
},
],
}
]

chain = MessageChain.model_validate(raw)
assert len(chain) == 1
fwd = chain[0]
assert isinstance(fwd, Forward)
assert len(fwd.node_list) == 2

# Verify nested message_chain is a proper MessageChain with typed components
alice_mc = fwd.node_list[0].message_chain
assert isinstance(alice_mc, MessageChain)
assert len(alice_mc) == 2
assert isinstance(alice_mc[0], Plain)
assert alice_mc[0].text == "Hello from Alice"
assert isinstance(alice_mc[1], Image)
assert alice_mc[1].url == "http://example.com/a.png"

bob_mc = fwd.node_list[1].message_chain
assert isinstance(bob_mc, MessageChain)
assert isinstance(bob_mc[0], Plain)
assert bob_mc[0].text == "Hello from Bob"


def test_forward_roundtrip_serialization():
"""测试 Forward 消息的序列化/反序列化往返"""
original = MessageChain([
Forward(
display=ForwardMessageDiaplay(title="Test"),
node_list=[
ForwardMessageNode(
sender_id="123",
sender_name="User",
message_chain=MessageChain([Plain(text="nested msg")]),
),
],
)
])

serialized = original.model_dump()
deserialized = MessageChain.model_validate(serialized)

fwd = deserialized[0]
assert isinstance(fwd, Forward)
assert isinstance(fwd.node_list[0].message_chain, MessageChain)
assert isinstance(fwd.node_list[0].message_chain[0], Plain)
assert fwd.node_list[0].message_chain[0].text == "nested msg"


def test_message_chain_with_image():
"""测试带图片的消息链"""
image = Image(image_id="test_image_id", url="http://example.com/image.jpg")
Expand Down