fix(frontend): restore thinking-only steps after reconnect (#7144)

* fix(frontend): restore thinking-only steps after reconnect

* docs(frontend): explain reconnect answer boundary

---------

Co-authored-by: TYY-2005 <TYY-2005@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
TYY-2005 2026-10-20 09:57:28 +08:00 committed by GitHub
parent f23a411325
commit 36ee986d55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 2 deletions

View File

@ -21,7 +21,7 @@ import { getBackendBaseURL } from "../config";
import { useI18n } from "../i18n/hooks";
import { getMessageRunId } from "../messages/run-duration";
import {
hasContent,
extractContentFromMessage,
hasToolCalls,
isHiddenFromUIMessage,
} from "../messages/utils";
@ -1072,7 +1072,9 @@ export function restoreReconnectedTurnMessageOrder(
if (
message.type === "ai" &&
!isHiddenFromUIMessage(message) &&
hasContent(message) &&
// Thinking-only blocks (Anthropic) are not completed answers: require
// extractable text/image content to close the turn.
extractContentFromMessage(message).length > 0 &&
!hasToolCalls(message)
) {
candidateStart = index + 1;

View File

@ -2503,6 +2503,43 @@ test("reconnected turn order treats a content-only streaming text as a boundary"
).toEqual([streamingText, human, toolStep, laterStep]);
});
test("reconnected turn order does not treat Anthropic thinking blocks as a completed answer", () => {
const thinkingStep = {
id: "thinking-step",
type: "ai",
content: [{ type: "thinking", thinking: "Still analyzing" }],
run_id: "run-r",
} as unknown as Message;
const toolStep = {
id: "tool-step",
type: "ai",
content: "",
tool_calls: [{ id: "tc-1", name: "read_file", args: {} }],
run_id: "run-r",
} as unknown as Message;
const human = {
id: "human-r",
type: "human",
content: "Question",
} as Message;
const laterStep = {
id: "later",
type: "ai",
content: "",
tool_calls: [{ id: "tc-2", name: "write_file", args: {} }],
run_id: "run-r",
} as unknown as Message;
expect(
restoreReconnectedTurnMessageOrder([
thinkingStep,
toolStep,
human,
laterStep,
]),
).toEqual([human, thinkingStep, toolStep, laterStep]);
});
test("rendered message ledger does not retain explicitly superseded messages", () => {
const retained = {
id: "retained-answer",