Fix nested hint transcript retry stability

This commit is contained in:
adarshh347 2026-11-05 11:05:40 +05:30
parent f3c07e7569
commit 11da577104
2 changed files with 84 additions and 20 deletions

View file

@ -153,26 +153,38 @@ def _canonical_message_json(msg: Msg) -> str:
return json.dumps(persisted, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def _stabilize_content_block(raw_block: dict, path: tuple[int, ...], message_timestamp: str | None) -> dict:
"""Stabilize defaults generated while parsing a raw content block."""
block = dict(raw_block)
if isinstance(block.get("hint"), list):
block["hint"] = [
_stabilize_content_block(child, (*path, index), message_timestamp) if isinstance(child, dict) else child
for index, child in enumerate(block["hint"])
]
if "id" not in block:
canonical = json.dumps(raw_block, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
position = str(path[0]) if len(path) == 1 else ".".join(map(str, path))
block["id"] = hashlib.sha256(f"{position}:{canonical}".encode("utf-8")).hexdigest()
if "created_at" not in block and message_timestamp:
block["created_at"] = message_timestamp
if block.get("type") == "hint" and "finished_at" not in block and message_timestamp:
block["finished_at"] = message_timestamp
return block
def _stabilize_raw_content_ids(item: dict) -> dict:
"""Give raw content blocks stable IDs when the caller did not supply one."""
"""Stabilize generated IDs and timestamps in raw (including nested hint) blocks."""
content = item.get("content")
if not isinstance(content, list):
return item
stable_content = []
changed = False
for index, raw_block in enumerate(content):
if not isinstance(raw_block, dict):
stable_content.append(raw_block)
continue
block = dict(raw_block)
if "id" not in block:
canonical = json.dumps(block, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
block["id"] = hashlib.sha256(f"{index}:{canonical}".encode("utf-8")).hexdigest()
if "created_at" not in block and item.get("created_at"):
block["created_at"] = item["created_at"]
stable_content.append(block)
changed = changed or block != raw_block
timestamp = item.get("created_at")
stable_content = [
_stabilize_content_block(block, (index,), timestamp) if isinstance(block, dict) else block
for index, block in enumerate(content)
]
changed = stable_content != content
return {**item, "content": stable_content} if changed else item

View file

@ -264,14 +264,29 @@ async def test_metadata_bool_to_int_correction_is_persisted(step, append_new):
@pytest.mark.asyncio
async def test_identical_raw_retry_with_generated_content_ids_does_not_rewrite(step, monkeypatch):
"""Parsing the same raw messages twice should not create false block-ID changes."""
@pytest.mark.parametrize(
"content",
[
[{"type": "text", "text": "same raw payload"}],
[{"type": "hint", "hint": "notification"}],
[
{
"type": "hint",
"hint": [{"type": "text", "text": "notification"}],
"finished_at": "2026-10-01T10:00:00",
},
],
],
ids=["text", "hint", "nested hint"],
)
async def test_identical_raw_retry_with_generated_content_defaults_does_not_rewrite(step, monkeypatch, content):
"""Parsing identical raw messages twice should not churn generated block defaults."""
raw = {
"id": "m0",
"name": "Alice",
"role": "user",
"name": "Assistant",
"role": "assistant",
"created_at": "2026-10-01T10:00:00",
"content": [{"type": "text", "text": "same raw payload"}],
"content": content,
}
first = step._build_messages([raw])
await step._save_session_messages(_SESSION, first)
@ -292,6 +307,43 @@ async def test_identical_raw_retry_with_generated_content_ids_does_not_rewrite(s
assert step._session_path(_SESSION).read_bytes() == original
def test_raw_hint_conversion_preserves_explicit_nested_ids_and_timestamps(step):
"""Caller-provided IDs and timestamps remain authoritative at every hint depth."""
message = step._to_msg(
{
"id": "m0",
"name": "Alice",
"role": "assistant",
"created_at": "2026-10-01T10:00:00",
"content": [
{
"type": "hint",
"id": "outer-id",
"created_at": "2026-10-01T10:00:01",
"finished_at": "2026-10-01T10:00:02",
"hint": [
{
"type": "text",
"text": "notification",
"id": "nested-id",
"created_at": "2026-10-01T10:00:03",
},
],
},
],
},
)
outer = message.content[0]
nested = outer.hint[0]
assert (outer.id, outer.created_at, outer.finished_at) == (
"outer-id",
"2026-10-01T10:00:01",
"2026-10-01T10:00:02",
)
assert (nested.id, nested.created_at) == ("nested-id", "2026-10-01T10:00:03")
def test_raw_message_conversion_preserves_explicit_content_block_ids(step):
"""Caller-provided block IDs remain authoritative during normalization."""
message = step._to_msg(