Skip to content

Commit 98b295c

Browse files
fix: address PR #89 review feedback on role coercion and fallback messages
1 parent 79197b2 commit 98b295c

2 files changed

Lines changed: 46 additions & 6 deletions

File tree

tests/test_jsonl_parser.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1041,16 +1041,34 @@ def test_unknown_entry_type_maps_to_system_with_warning(self, caplog):
10411041
]
10421042
)
10431043
try:
1044-
with caplog.at_level("WARNING"):
1044+
with caplog.at_level("WARNING", logger="utils.jsonl_parser"):
10451045
s = parse_session(path)
10461046
assert len(s["messages"]) == 1
10471047
assert s["messages"][0]["role"] == "system"
1048+
assert s["messages"][0]["text"] == "forward-compat payload"
10481049
assert s["messages"][0]["content"] == "forward-compat payload"
10491050
assert "Unknown message role" in caplog.text
10501051
assert "mystery_future_type" in caplog.text
10511052
finally:
10521053
os.unlink(path)
10531054

1055+
def test_fallback_message_extracts_text_from_structured_content(self):
1056+
path = _write_jsonl(
1057+
[
1058+
{
1059+
"type": "result",
1060+
"timestamp": "2026-01-01T00:00:00Z",
1061+
"content": [{"type": "text", "text": "block body"}],
1062+
}
1063+
]
1064+
)
1065+
try:
1066+
s = parse_session(path)
1067+
assert s["messages"][0]["text"] == "block body"
1068+
assert s["messages"][0]["content"] == "block body"
1069+
finally:
1070+
os.unlink(path)
1071+
10541072
def test_valid_unhandled_result_type_emits_result_role(self):
10551073
path = _write_jsonl(
10561074
[
@@ -1067,3 +1085,19 @@ def test_valid_unhandled_result_type_emits_result_role(self):
10671085
assert s["messages"][0]["role"] == "result"
10681086
finally:
10691087
os.unlink(path)
1088+
1089+
1090+
class TestCoerceRole:
1091+
def test_known_roles_returned_unchanged(self):
1092+
from utils.jsonl_parser import _coerce_role
1093+
1094+
for role in ("user", "assistant", "system", "result", "progress"):
1095+
assert _coerce_role(role) == role
1096+
1097+
def test_unknown_role_maps_to_system_with_warning(self, caplog):
1098+
from utils.jsonl_parser import _coerce_role
1099+
1100+
with caplog.at_level("WARNING", logger="utils.jsonl_parser"):
1101+
result = _coerce_role("totally_unknown")
1102+
assert result == "system"
1103+
assert "totally_unknown" in caplog.text

utils/jsonl_parser.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,33 @@
4141
"_track_file_activity",
4242
]
4343

44-
_HANDLED_ENTRY_TYPES = frozenset({"user", "assistant", "system", "progress"})
4544
_SKIP_ENTRY_TYPES = frozenset({"file-history-snapshot"})
4645
_VALID_ROLES = frozenset(get_args(RoleLiteral))
46+
_log = logging.getLogger(__name__)
4747

4848

4949
def _coerce_role(raw: str) -> RoleLiteral:
5050
if raw in _VALID_ROLES:
5151
return cast(RoleLiteral, raw)
52-
logging.warning("Unknown message role %r; mapping to 'system'", raw)
52+
_log.warning("Unknown message role %r; mapping to 'system'", raw)
5353
return "system"
5454

5555

5656
def _fallback_message(entry: dict[str, Any], role: RoleLiteral) -> MessageDict:
5757
"""Minimal message for JSONL entry types without a dedicated processor."""
58-
content = entry.get("content", "")
59-
text = content if isinstance(content, str) else (str(content) if content is not None else "")
58+
raw_content = entry.get("content", "")
59+
if isinstance(raw_content, str):
60+
text = raw_content
61+
elif raw_content is not None:
62+
text = _extract_text(_normalize_content(raw_content))
63+
else:
64+
text = ""
6065
return {
6166
"role": role,
6267
"uuid": entry.get("uuid"),
6368
"parent_uuid": entry.get("parentUuid"),
6469
"timestamp": entry.get("timestamp"),
70+
"text": text,
6571
"content": text,
6672
"is_sidechain": entry.get("isSidechain", False),
6773
}
@@ -172,7 +178,7 @@ def parse_session(filepath: str) -> SessionDict:
172178
_process_progress(entry, messages)
173179
elif entry_type:
174180
type_str = entry_type if isinstance(entry_type, str) else str(entry_type)
175-
if type_str not in _HANDLED_ENTRY_TYPES and type_str not in _SKIP_ENTRY_TYPES:
181+
if type_str not in _SKIP_ENTRY_TYPES:
176182
messages.append(_fallback_message(entry, _coerce_role(type_str)))
177183

178184
metadata["models_used"] = sorted(metadata["models_used"])

0 commit comments

Comments
 (0)