feat(frontends,ui): opus 5.5 progress updates show in the web panel and the vault transcript

This commit is contained in:
hh committed 2026-09-22 20:36:23 +02:00
1 parent bbce425eeb
commit 13873f857b
5 files changed
+125 -72

No files matched your search

@@ -255,8 +255,9 @@ def _summarize_assistant_group(
) -> tuple[str, list[str], int]:
"""Compute (spoken_text, tool_skeleton, text_segment_count) for a display group.
Must mirror ``parser.parse_assistant_structure``: text blocks collapse into
one segment, tool_use becomes a skeleton entry, tool_result/thinking are invisible.
Must mirror what the renderer writes and the parser reads back: text and
non-empty thinking collapse into one segment, tool_use becomes a skeleton
entry, tool_result and empty thinking are invisible.
"""
from beaver_gateway.frontends.markdown.parser import TextSegment, ToolSegment
@@ -282,12 +283,10 @@ def _summarize_assistant_group(
for blk in content:
if not isinstance(blk, dict):
continue
btype = blk.get("type")
if btype == "text":
text = str(blk.get("text", "")).strip()
if text:
pending.append(text)
elif btype == "tool_use":
text = _spoken(blk)
if text:
pending.append(text)
elif blk.get("type") == "tool_use":
_flush()
segments.append(ToolSegment(name=str(blk.get("name", ""))))
_flush()
@@ -440,11 +439,16 @@ def _splice_assistant_group(
return _splice_by_rebuild(stored_group=stored_group, incoming=incoming)
def _is_text_block(blk: object) -> bool:
def _spoken(blk: object) -> str:
if not isinstance(blk, dict):
return False
return ""
block = cast("dict[str, Any]", blk)
return block.get("type") == "text" and bool(str(block.get("text", "")).strip())
kind = block.get("type")
if kind == "text":
return str(block.get("text", "")).strip()
if kind == "thinking":
return str(block.get("thinking", "")).strip()
return ""
def _splice_in_place(
@@ -454,7 +458,8 @@ def _splice_in_place(
Preserves message/thinking-block boundaries that a rebuild would lose
(and that a cache-hit continuation depends on); returns ``None`` if
text blocks aren't 1:1 with incoming segments, so the caller rebuilds instead.
text blocks aren't 1:1 with incoming segments or the prose runs through
signed thinking blocks, so the caller rebuilds instead.
"""
from beaver_gateway.frontends.markdown.parser import TextSegment
@@ -466,9 +471,12 @@ def _splice_in_place(
content = msg.get("content")
if msg["role"] != "assistant" or not isinstance(content, list):
continue
positions.extend(
(mi, bi) for bi, blk in enumerate(content) if _is_text_block(blk)
)
for bi, blk in enumerate(cast("list[dict[str, Any]]", content)):
if not _spoken(blk):
continue
if blk["type"] == "thinking":
return None
positions.append((mi, bi))
if len(positions) != len(new_texts):
return None