fix(tui): keep reasoning-only assistant turns visible on session resume
A thinking-only assistant turn (reasoning present, empty visible text) is persisted with its reasoning fields and stays recallable from the transcript, but `_history_to_messages` dropped it as "empty" before its reasoning was attached. On desktop/TUI resume or reload the turn therefore vanished from the session view while the agent could still recall it from a fresh session -- exactly the "messages disappear when the LLM uses its thinking block, but a new session can recall them" symptom reported on #44022. Keep an assistant turn when it carries reasoning, even with empty text, so the desktop "Thinking…" disclosure has something to render. Genuinely empty turns (no text, no reasoning, no tool calls) are still filtered out. Refs #44022 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
643dc82793
commit
2667601c05
2 changed files with 53 additions and 7 deletions
|
|
@ -847,6 +847,41 @@ def test_history_to_messages_preserves_tool_calls_for_resume_display():
|
|||
]
|
||||
|
||||
|
||||
def test_history_to_messages_keeps_reasoning_only_assistant_turn():
|
||||
# A thinking-only assistant turn (reasoning present, no visible text) is
|
||||
# persisted and recallable, but was dropped from the resumed session view
|
||||
# as "empty" -- so it vanished while the agent could still recall it from
|
||||
# the transcript. Keep it (with reasoning) so the desktop "Thinking…"
|
||||
# disclosure renders. (#44022)
|
||||
history = [
|
||||
{"role": "user", "content": "think about this"},
|
||||
{"role": "assistant", "content": "", "reasoning": "step-by-step thoughts"},
|
||||
{"role": "assistant", "content": "here is the answer"},
|
||||
]
|
||||
|
||||
assert server._history_to_messages(history) == [
|
||||
{"role": "user", "text": "think about this"},
|
||||
{"role": "assistant", "text": "", "reasoning": "step-by-step thoughts"},
|
||||
{"role": "assistant", "text": "here is the answer"},
|
||||
]
|
||||
|
||||
|
||||
def test_history_to_messages_still_drops_empty_assistant_without_reasoning():
|
||||
# A genuinely empty assistant turn (no text, no reasoning, no tool calls)
|
||||
# remains filtered out -- the fix only spares reasoning-bearing turns.
|
||||
history = [
|
||||
{"role": "user", "content": "hi"},
|
||||
{"role": "assistant", "content": "", "reasoning": ""},
|
||||
{"role": "assistant", "content": " "},
|
||||
{"role": "assistant", "content": "real reply"},
|
||||
]
|
||||
|
||||
assert server._history_to_messages(history) == [
|
||||
{"role": "user", "text": "hi"},
|
||||
{"role": "assistant", "text": "real reply"},
|
||||
]
|
||||
|
||||
|
||||
def test_history_to_messages_renders_multimodal_content():
|
||||
# bb/gui preserves image URLs in the resume payload so the desktop
|
||||
# renderer's extractEmbeddedImages can pull them back out and display
|
||||
|
|
|
|||
|
|
@ -3708,16 +3708,27 @@ def _history_to_messages(history: list[dict]) -> list[dict]:
|
|||
{"role": "tool", "name": name, "context": _tool_ctx(name, args)}
|
||||
)
|
||||
continue
|
||||
if not content_text.strip():
|
||||
# An assistant turn may carry only reasoning/thinking content with no
|
||||
# visible text (extended-thinking turns, thinking-only recovery
|
||||
# responses). Such a turn is persisted with its reasoning fields and is
|
||||
# recallable from the transcript, but dropping it here as "empty" makes
|
||||
# it vanish from the resumed/reloaded session view while the desktop's
|
||||
# reasoning disclosure has nothing to render. Keep it when it carries
|
||||
# reasoning so the "Thinking…" block still shows. (#44022)
|
||||
reasoning_keys = (
|
||||
"reasoning",
|
||||
"reasoning_content",
|
||||
"reasoning_details",
|
||||
"codex_reasoning_items",
|
||||
)
|
||||
has_reasoning = role == "assistant" and any(
|
||||
m.get(key) for key in reasoning_keys
|
||||
)
|
||||
if not content_text.strip() and not has_reasoning:
|
||||
continue
|
||||
msg = {"role": role, "text": content_text}
|
||||
if role == "assistant":
|
||||
for key in (
|
||||
"reasoning",
|
||||
"reasoning_content",
|
||||
"reasoning_details",
|
||||
"codex_reasoning_items",
|
||||
):
|
||||
for key in reasoning_keys:
|
||||
if key in m and m.get(key) is not None:
|
||||
msg[key] = m.get(key)
|
||||
messages.append(msg)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue