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

+76 -21
View File
@@ -1,6 +1,7 @@
from anthropic.types import Message, TextBlock, ThinkingBlock, ToolUseBlock, Usage
from beaver_gateway.frontends.markdown import renderer
from beaver_gateway.frontends.markdown import history, parser, renderer
from beaver_gateway.frontends.markdown.parser import TextSegment, ToolSegment
def _message(*blocks: object) -> Message:
@@ -29,31 +30,85 @@ def test_tool_calls_leave_no_blank_lines() -> None:
)
def test_thinking_renders_as_collapsed_callout() -> None:
message = _message(
ThinkingBlock(type="thinking", thinking="a\nb", signature="s"),
TextBlock(type="text", text="answer"),
)
assert renderer.render_assistant_message(message) == (
"### Assistant:\n\n> [!thinking]-\n> a\n> b\n\nanswer\n"
def _progress_turn() -> Message:
return _message(
TextBlock(type="text", text="Checking the containers."),
ThinkingBlock(type="thinking", thinking="", signature="s"),
ToolUseBlock(type="tool_use", id="t1", name="komodo", input={}),
ThinkingBlock(type="thinking", thinking="", signature="s"),
ThinkingBlock(type="thinking", thinking="All up, reading logs.", signature="s"),
ToolUseBlock(type="tool_use", id="t2", name="komodo", input={}),
ThinkingBlock(type="thinking", thinking="Found it.\nOne more.", signature="s"),
ToolUseBlock(type="tool_use", id="t3", name="komodo", input={}),
TextBlock(type="text", text="The stack is healthy."),
)
def test_thinking_between_hidden_tool_calls_collapses_to_the_first() -> None:
message = _message(
ThinkingBlock(type="thinking", thinking="one", signature="s"),
ToolUseBlock(type="tool_use", id="t1", name="Read", input={}),
ThinkingBlock(type="thinking", thinking="two", signature="s"),
ToolUseBlock(type="tool_use", id="t2", name="Read", input={}),
ThinkingBlock(type="thinking", thinking="three", signature="s"),
TextBlock(type="text", text="answer"),
ThinkingBlock(type="thinking", thinking="four", signature="s"),
TextBlock(type="text", text="more"),
def test_progress_renders_as_text_in_place_and_empty_thinking_vanishes() -> None:
assert renderer.render_assistant_message(_progress_turn()) == (
"### Assistant:\n\nChecking the containers.\n\nAll up, reading logs.\n\n"
"Found it.\nOne more.\n\nThe stack is healthy.\n"
)
assert renderer.render_assistant_message(message) == (
"### Assistant:\n\n> [!thinking]-\n> one\n\nanswer\n\n"
"> [!thinking]-\n> four\n\nmore\n"
def test_progress_round_trips_through_the_parser_and_the_stored_history() -> None:
message = _progress_turn()
body = renderer.render_user_text("status?") + renderer.render_assistant_message(
message
)
turns = parser.parse(body + renderer.render_user_text("thanks")).turns
assert turns[1].structure == (
TextSegment(
text="Checking the containers.\n\nAll up, reading logs.\n\n"
"Found it.\nOne more.\n\nThe stack is healthy."
),
)
stored = [
{"role": "user", "content": "status?"},
{"role": "assistant", "content": [b.model_dump() for b in message.content]},
]
outcome = history.diff_and_fork(stored=stored, incoming=turns)
assert outcome.divergence_index is None
assert not outcome.edited
assert outcome.messages[1] == stored[1]
def test_an_edited_progress_turn_rebuilds_instead_of_rewriting_signed_blocks() -> None:
message = _progress_turn()
body = renderer.render_assistant_message(message).replace("Found it", "Fixed it")
turns = parser.parse(
renderer.render_user_text("status?") + body + renderer.render_user_text("ok")
).turns
stored = [
{"role": "user", "content": "status?"},
{"role": "assistant", "content": [b.model_dump() for b in message.content]},
]
outcome = history.diff_and_fork(stored=stored, incoming=turns)
assert outcome.edited
assert outcome.messages[1] == {
"role": "assistant",
"content": [{"type": "text", "text": turns[1].text}],
}
def test_a_file_with_collapsed_thinking_callouts_parses_as_before() -> None:
body = (
"### User:\n\nq\n\n---\n\n### Assistant:\n\n"
"> [!thinking]-\n> weighing it\n> twice\n\nanswer\n\n"
"> [!tool]- Read · notes.md\n> input\n\n"
"> [!thinking]-\n>\n\nmore\n\n---\n\n### User:\n\nnext\n"
)
parsed = parser.parse(body)
assert parsed.turns[1].structure == (
TextSegment(text="answer"),
ToolSegment(name="Read"),
TextSegment(text="more"),
)
assert parsed.messages == [
{"role": "user", "content": "q"},
{"role": "assistant", "content": "answer\n\nmore"},
{"role": "user", "content": "next"},
]
def test_user_scaffold_matches_rendered_user_turn_shape() -> None: