Skip to content

Commit e6628f9

Browse files
authored
fix: reload legacy session rows (#1365)
* fix(message): reload legacy session rows * fix(message): reload legacy session rows
1 parent 37e106a commit e6628f9

2 files changed

Lines changed: 58 additions & 4 deletions

File tree

openviking/message/message.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from typing import List, Literal, Optional
1212

1313
from openviking.message.part import ContextPart, Part, TextPart, ToolPart
14-
from openviking.utils.time_utils import format_iso8601, parse_iso_datetime
1514

1615

1716
@dataclass
@@ -65,6 +64,12 @@ def estimated_tokens(self) -> int:
6564
def to_dict(self) -> dict:
6665
"""Serialize to JSONL."""
6766
created_at_val = self.created_at or datetime.now(timezone.utc).isoformat()
67+
if isinstance(created_at_val, datetime):
68+
created_at_val = (
69+
created_at_val.astimezone(timezone.utc)
70+
.isoformat(timespec="milliseconds")
71+
.replace("+00:00", "Z")
72+
)
6873
return {
6974
"id": self.id,
7075
"role": self.role,
@@ -108,7 +113,15 @@ def _part_to_dict(self, part: Part) -> dict:
108113
def from_dict(cls, data: dict) -> "Message":
109114
"""Deserialize from JSONL."""
110115
parts = []
111-
for p in data.get("parts", []):
116+
raw_parts = data.get("parts")
117+
if raw_parts is None:
118+
legacy_content = data.get("content")
119+
if legacy_content is not None:
120+
raw_parts = [{"type": "text", "text": legacy_content}]
121+
else:
122+
raw_parts = []
123+
124+
for p in raw_parts:
112125
if p["type"] == "text":
113126
parts.append(TextPart(text=p.get("text", "")))
114127
elif p["type"] == "context":
@@ -138,7 +151,7 @@ def from_dict(cls, data: dict) -> "Message":
138151
id=data["id"],
139152
role=data["role"],
140153
parts=parts,
141-
created_at=data["created_at"],
154+
created_at=data.get("created_at"),
142155
)
143156

144157
@classmethod
@@ -193,7 +206,7 @@ def create_assistant(
193206
id=msg_id or f"msg_{uuid4().hex}",
194207
role="assistant",
195208
parts=parts,
196-
created_at=datetime.now(timezone.utc).isoformat()
209+
created_at=datetime.now(timezone.utc).isoformat(),
197210
)
198211

199212
def get_context_parts(self) -> List[ContextPart]:

tests/unit/test_message.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,24 @@ def test_from_dict_with_tool_part(self):
514514
assert isinstance(msg.parts[0], ToolPart)
515515
assert msg.parts[0].tool_id == "call-1"
516516

517+
def test_from_dict_supports_legacy_content_only_messages(self):
518+
"""Legacy messages with only content should load as a TextPart."""
519+
d = {
520+
"id": "msg-legacy",
521+
"role": "user",
522+
"content": "Hello from legacy storage",
523+
}
524+
525+
msg = Message.from_dict(d)
526+
527+
assert msg.id == "msg-legacy"
528+
assert msg.role == "user"
529+
assert len(msg.parts) == 1
530+
assert isinstance(msg.parts[0], TextPart)
531+
assert msg.parts[0].text == "Hello from legacy storage"
532+
assert msg.content == "Hello from legacy storage"
533+
assert msg.created_at is None
534+
517535
def test_roundtrip(self):
518536
"""Test to_dict -> from_dict roundtrip."""
519537
original = Message(
@@ -534,6 +552,29 @@ def test_roundtrip(self):
534552
assert isinstance(restored.parts[0], TextPart)
535553
assert isinstance(restored.parts[1], ContextPart)
536554

555+
def test_legacy_message_can_be_reloaded_and_extended(self):
556+
"""Legacy content-only rows should survive reload before appending new messages."""
557+
legacy_row = {
558+
"id": "msg-legacy",
559+
"role": "user",
560+
"content": "Legacy message",
561+
"created_at": "2026-03-26T10:30:00Z",
562+
}
563+
fresh = Message.create_user("Fresh message", msg_id="msg-fresh")
564+
565+
reloaded_messages = [Message.from_dict(legacy_row), Message.from_dict(fresh.to_dict())]
566+
567+
assert [message.content for message in reloaded_messages] == [
568+
"Legacy message",
569+
"Fresh message",
570+
]
571+
assert [
572+
json.loads(message.to_jsonl())["parts"][0]["text"] for message in reloaded_messages
573+
] == [
574+
"Legacy message",
575+
"Fresh message",
576+
]
577+
537578

538579
class TestMessageFactoryMethods:
539580
"""Test Message factory methods."""

0 commit comments

Comments
 (0)