feat(agents,backends,conversations,frontends,ui): progress thinking is told apart by its agent, raycast reasoning stays collapsed, read_conversation keeps progress
This commit is contained in:
1 parent
13873f857b
commit
120985deab
18 files changed
+374
-110
No files matched your search
@@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Any, cast
|
||||
from sqlalchemy import delete
|
||||
from sqlmodel import select
|
||||
|
||||
from beaver_gateway.backends.transcript import spoken_text
|
||||
from beaver_gateway.storage.models import Conversation, ConversationMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -180,7 +181,9 @@ class _StoredDisplayTurn:
|
||||
text_segment_count: int
|
||||
|
||||
|
||||
def _group_display_turns(stored: list[dict[str, Any]]) -> list[_StoredDisplayTurn]:
|
||||
def _group_display_turns(
|
||||
stored: list[dict[str, Any]], *, thinking_is_progress: bool
|
||||
) -> list[_StoredDisplayTurn]:
|
||||
"""Walk raw stored messages, group them into Obsidian-visible turns.
|
||||
|
||||
A user-prompt message (no ``tool_result`` blocks) opens a new turn;
|
||||
@@ -210,7 +213,9 @@ def _group_display_turns(stored: list[dict[str, Any]]) -> list[_StoredDisplayTur
|
||||
break
|
||||
group.append(m)
|
||||
i += 1
|
||||
spoken, skeleton, text_count = _summarize_assistant_group(group)
|
||||
spoken, skeleton, text_count = _summarize_assistant_group(
|
||||
group, thinking_is_progress=thinking_is_progress
|
||||
)
|
||||
out.append(
|
||||
_StoredDisplayTurn(
|
||||
role="assistant",
|
||||
@@ -251,13 +256,13 @@ def _user_prompt_text(content: Any) -> str:
|
||||
|
||||
|
||||
def _summarize_assistant_group(
|
||||
group: list[dict[str, Any]],
|
||||
group: list[dict[str, Any]], *, thinking_is_progress: bool
|
||||
) -> tuple[str, list[str], int]:
|
||||
"""Compute (spoken_text, tool_skeleton, text_segment_count) for a display group.
|
||||
|
||||
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.
|
||||
progress thinking collapse into one segment, tool_use becomes a skeleton
|
||||
entry, tool_result, reasoning and empty thinking are invisible.
|
||||
"""
|
||||
from beaver_gateway.frontends.markdown.parser import TextSegment, ToolSegment
|
||||
|
||||
@@ -283,7 +288,7 @@ def _summarize_assistant_group(
|
||||
for blk in content:
|
||||
if not isinstance(blk, dict):
|
||||
continue
|
||||
text = _spoken(blk)
|
||||
text = spoken_text(blk, thinking_is_progress=thinking_is_progress)
|
||||
if text:
|
||||
pending.append(text)
|
||||
elif blk.get("type") == "tool_use":
|
||||
@@ -298,7 +303,10 @@ def _summarize_assistant_group(
|
||||
|
||||
|
||||
def diff_and_fork(
|
||||
*, stored: list[dict[str, Any]], incoming: list[ParsedTurn]
|
||||
*,
|
||||
stored: list[dict[str, Any]],
|
||||
incoming: list[ParsedTurn],
|
||||
thinking_is_progress: bool,
|
||||
) -> ForkOutcome:
|
||||
"""Align the incoming parsed file against stored history.
|
||||
|
||||
@@ -313,11 +321,15 @@ def diff_and_fork(
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
stored_groups = _group_display_turns(stored)
|
||||
stored_groups = _group_display_turns(
|
||||
stored, thinking_is_progress=thinking_is_progress
|
||||
)
|
||||
new_user_turn = incoming[-1]
|
||||
prior_incoming = incoming[:-1]
|
||||
|
||||
spliced_groups, divergence, edited = _walk_prefix(prior_incoming, stored_groups)
|
||||
spliced_groups, divergence, edited = _walk_prefix(
|
||||
prior_incoming, stored_groups, thinking_is_progress=thinking_is_progress
|
||||
)
|
||||
|
||||
if divergence is None and len(prior_incoming) < len(stored_groups):
|
||||
if _file_lags_store(stored_groups, len(prior_incoming), new_user_turn):
|
||||
@@ -357,7 +369,10 @@ def _file_lags_store(
|
||||
|
||||
|
||||
def _walk_prefix(
|
||||
prior_incoming: list[ParsedTurn], stored_groups: list[_StoredDisplayTurn]
|
||||
prior_incoming: list[ParsedTurn],
|
||||
stored_groups: list[_StoredDisplayTurn],
|
||||
*,
|
||||
thinking_is_progress: bool,
|
||||
) -> tuple[list[list[dict[str, Any]]], int | None, bool]:
|
||||
"""Walk incoming vs stored side-by-side until first divergence.
|
||||
|
||||
@@ -390,7 +405,9 @@ def _walk_prefix(
|
||||
continue
|
||||
if inc_skeleton and inc_text_count != st.text_segment_count:
|
||||
return spliced_groups, i, edited
|
||||
spliced = _splice_assistant_group(stored_group=st, incoming=inc)
|
||||
spliced = _splice_assistant_group(
|
||||
stored_group=st, incoming=inc, thinking_is_progress=thinking_is_progress
|
||||
)
|
||||
if spliced is None:
|
||||
return spliced_groups, i, edited
|
||||
spliced_groups.append(spliced)
|
||||
@@ -425,7 +442,10 @@ def _assemble_tail(
|
||||
|
||||
|
||||
def _splice_assistant_group(
|
||||
*, stored_group: _StoredDisplayTurn, incoming: ParsedTurn
|
||||
*,
|
||||
stored_group: _StoredDisplayTurn,
|
||||
incoming: ParsedTurn,
|
||||
thinking_is_progress: bool,
|
||||
) -> list[dict[str, Any]] | None:
|
||||
"""Apply the file's prose to a stored assistant turn.
|
||||
|
||||
@@ -433,26 +453,21 @@ def _splice_assistant_group(
|
||||
markdown can't express. Falls back to rebuilding from the file's
|
||||
structure when the text blocks don't line up one-to-one.
|
||||
"""
|
||||
preserved = _splice_in_place(stored_group=stored_group, incoming=incoming)
|
||||
preserved = _splice_in_place(
|
||||
stored_group=stored_group,
|
||||
incoming=incoming,
|
||||
thinking_is_progress=thinking_is_progress,
|
||||
)
|
||||
if preserved is not None:
|
||||
return preserved
|
||||
return _splice_by_rebuild(stored_group=stored_group, incoming=incoming)
|
||||
|
||||
|
||||
def _spoken(blk: object) -> str:
|
||||
if not isinstance(blk, dict):
|
||||
return ""
|
||||
block = cast("dict[str, Any]", blk)
|
||||
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(
|
||||
*, stored_group: _StoredDisplayTurn, incoming: ParsedTurn
|
||||
*,
|
||||
stored_group: _StoredDisplayTurn,
|
||||
incoming: ParsedTurn,
|
||||
thinking_is_progress: bool,
|
||||
) -> list[dict[str, Any]] | None:
|
||||
"""Copy the stored messages, swapping only their text block contents.
|
||||
|
||||
@@ -472,7 +487,7 @@ def _splice_in_place(
|
||||
if msg["role"] != "assistant" or not isinstance(content, list):
|
||||
continue
|
||||
for bi, blk in enumerate(cast("list[dict[str, Any]]", content)):
|
||||
if not _spoken(blk):
|
||||
if not spoken_text(blk, thinking_is_progress=thinking_is_progress):
|
||||
continue
|
||||
if blk["type"] == "thinking":
|
||||
return None
|
||||
|
||||
Reference in new issue
Block a user