Skip to content

Commit 8cebd29

Browse files
authored
fix: deserialize Forward message_chain in MessageChain.model_validate (#38)
The existing model_validate only recursively deserializes fields with annotation == MessageChain, but Forward's node_list contains ForwardMessageNode objects whose message_chain field has annotation Optional[MessageChain], which was not matched. This adds explicit handling for Forward's node_list to recursively deserialize each node's message_chain, similar to the existing special handling for Source's time field.
1 parent e728d76 commit 8cebd29

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

src/langbot_plugin/api/entities/builtin/platform/message.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,15 @@ def model_validate(cls, obj):
184184
item[field_name] = MessageChain.model_validate(
185185
field_value
186186
)
187+
# Special handling for Forward: recursively deserialize
188+
# message_chain inside each ForwardMessageNode
189+
if component_type == "Forward" and "node_list" in item:
190+
for node_data in item["node_list"]:
191+
if isinstance(node_data, dict) and "message_chain" in node_data:
192+
mc = node_data["message_chain"]
193+
if isinstance(mc, list):
194+
node_data["message_chain"] = MessageChain.model_validate(mc)
195+
187196
# Special processing of the time field of the Source class
188197
if component_type == "Source" and "time" in item:
189198
item["time"] = datetime.fromtimestamp(item["time"])

tests/test_message.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,87 @@ def test_message_chain_with_forward():
228228
assert chain[0].display.title == "Test Forward"
229229

230230

231+
def test_forward_message_chain_deserialization():
232+
"""测试 Forward 消息的 node_list 中 message_chain 的反序列化"""
233+
# Simulate raw dict data as it would come over the wire (e.g. from plugin runtime)
234+
raw = [
235+
{
236+
"type": "Forward",
237+
"display": {
238+
"title": "Chat history",
239+
"brief": "[Chat history]",
240+
"source": "Chat history",
241+
"preview": [],
242+
"summary": "View 2 forwarded messages",
243+
},
244+
"node_list": [
245+
{
246+
"sender_id": "111",
247+
"sender_name": "Alice",
248+
"message_chain": [
249+
{"type": "Plain", "text": "Hello from Alice"},
250+
{"type": "Image", "url": "http://example.com/a.png"},
251+
],
252+
"message_id": 1,
253+
},
254+
{
255+
"sender_id": "222",
256+
"sender_name": "Bob",
257+
"message_chain": [
258+
{"type": "Plain", "text": "Hello from Bob"},
259+
],
260+
"message_id": 2,
261+
},
262+
],
263+
}
264+
]
265+
266+
chain = MessageChain.model_validate(raw)
267+
assert len(chain) == 1
268+
fwd = chain[0]
269+
assert isinstance(fwd, Forward)
270+
assert len(fwd.node_list) == 2
271+
272+
# Verify nested message_chain is a proper MessageChain with typed components
273+
alice_mc = fwd.node_list[0].message_chain
274+
assert isinstance(alice_mc, MessageChain)
275+
assert len(alice_mc) == 2
276+
assert isinstance(alice_mc[0], Plain)
277+
assert alice_mc[0].text == "Hello from Alice"
278+
assert isinstance(alice_mc[1], Image)
279+
assert alice_mc[1].url == "http://example.com/a.png"
280+
281+
bob_mc = fwd.node_list[1].message_chain
282+
assert isinstance(bob_mc, MessageChain)
283+
assert isinstance(bob_mc[0], Plain)
284+
assert bob_mc[0].text == "Hello from Bob"
285+
286+
287+
def test_forward_roundtrip_serialization():
288+
"""测试 Forward 消息的序列化/反序列化往返"""
289+
original = MessageChain([
290+
Forward(
291+
display=ForwardMessageDiaplay(title="Test"),
292+
node_list=[
293+
ForwardMessageNode(
294+
sender_id="123",
295+
sender_name="User",
296+
message_chain=MessageChain([Plain(text="nested msg")]),
297+
),
298+
],
299+
)
300+
])
301+
302+
serialized = original.model_dump()
303+
deserialized = MessageChain.model_validate(serialized)
304+
305+
fwd = deserialized[0]
306+
assert isinstance(fwd, Forward)
307+
assert isinstance(fwd.node_list[0].message_chain, MessageChain)
308+
assert isinstance(fwd.node_list[0].message_chain[0], Plain)
309+
assert fwd.node_list[0].message_chain[0].text == "nested msg"
310+
311+
231312
def test_message_chain_with_image():
232313
"""测试带图片的消息链"""
233314
image = Image(image_id="test_image_id", url="http://example.com/image.jpg")

0 commit comments

Comments
 (0)