feat(frontends,ui): opus 5.5 progress updates show in the web panel and the vault transcript
This commit is contained in:
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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Render Anthropic ``Message`` (and user turns) into markdown.
|
||||
|
||||
One-way: produces the human-facing artifact only. The parser strips
|
||||
tool/thinking callouts separately when reshaping history for backend replay.
|
||||
the tool/thinking callouts of older files when reshaping history for replay.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -12,8 +12,6 @@ from typing import TYPE_CHECKING, Any, cast
|
||||
from anthropic.types import Message, TextBlock, ThinkingBlock
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable
|
||||
|
||||
from anthropic.types import MessageParam
|
||||
|
||||
|
||||
@@ -48,21 +46,14 @@ def render_assistant_text(text: str) -> str:
|
||||
def render_assistant_message(message: Message) -> str:
|
||||
"""Render an assistant ``Message`` into a turn block.
|
||||
|
||||
Tool-use blocks render to nothing; of the thinking blocks between two
|
||||
texts only the first one reaches the file.
|
||||
Tool-use blocks and empty thinking render to nothing; a non-empty thinking
|
||||
block is what the model wrote between tool calls and renders as text.
|
||||
"""
|
||||
parts: list[str] = ["### Assistant:", ""]
|
||||
after_thinking = False
|
||||
for block in message.content:
|
||||
thinking = isinstance(block, ThinkingBlock)
|
||||
if thinking and after_thinking:
|
||||
continue
|
||||
lines = list(_render_block(block))
|
||||
if not lines:
|
||||
continue
|
||||
after_thinking = thinking
|
||||
parts.extend(lines)
|
||||
parts.append("")
|
||||
text = _spoken(block)
|
||||
if text:
|
||||
parts.extend((text, ""))
|
||||
return "\n".join(parts).rstrip() + "\n"
|
||||
|
||||
|
||||
@@ -108,21 +99,12 @@ def summarize_tool_input(name: str, tool_input: object) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def _render_block(block: object) -> Iterable[str]:
|
||||
def _spoken(block: object) -> str:
|
||||
if isinstance(block, TextBlock):
|
||||
text = (block.text or "").strip()
|
||||
if text:
|
||||
yield text
|
||||
return
|
||||
return (block.text or "").strip()
|
||||
if isinstance(block, ThinkingBlock):
|
||||
yield from _render_thinking(block.thinking or "")
|
||||
return
|
||||
|
||||
|
||||
def _render_thinking(text: str) -> Iterable[str]:
|
||||
yield "> [!thinking]-"
|
||||
for line in text.strip().splitlines() or [""]:
|
||||
yield f"> {line}" if line else ">"
|
||||
return (block.thinking or "").strip()
|
||||
return ""
|
||||
|
||||
|
||||
def adaptive_fence(content: str) -> str:
|
||||
|
||||
Reference in new issue
Block a user