fix(webui): keep late reasoning attached above the answer

Some providers only surface structured `reasoning_content` after answer
text has already streamed. The WebUI was treating those late
`reasoning_delta` frames as a fresh assistant placeholder, so the
Thinking bubble rendered below the already-visible answer.

Attach late reasoning back to the active assistant turn instead. The
bubble still renders above the message content, preserving the expected
Thinking -> answer order even when the provider protocol delivers the
reasoning post-hoc. Added a regression test for answer-first followed by
reasoning_delta/reasoning_end.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Xubin Ren
2026-05-13 07:20:36 +00:00
co-authored by Cursor
parent 458b4ba235
commit 9829cf66d2
2 changed files with 40 additions and 5 deletions
+29
View File
@@ -209,6 +209,35 @@ describe("useNanobotStream", () => {
expect(result.current.messages[0].reasoningStreaming).toBe(false);
});
it("attaches post-hoc reasoning to the same assistant turn above the answer", () => {
const fake = fakeClient();
const { result } = renderHook(() => useNanobotStream("chat-r5", EMPTY_MESSAGES), {
wrapper: wrap(fake.client),
});
act(() => {
fake.emit("chat-r5", {
event: "delta",
chat_id: "chat-r5",
text: "hi~",
});
fake.emit("chat-r5", { event: "stream_end", chat_id: "chat-r5" });
fake.emit("chat-r5", {
event: "reasoning_delta",
chat_id: "chat-r5",
text: "This reasoning arrived after the answer stream.",
});
fake.emit("chat-r5", { event: "reasoning_end", chat_id: "chat-r5" });
});
expect(result.current.messages).toHaveLength(1);
expect(result.current.messages[0].content).toBe("hi~");
expect(result.current.messages[0].reasoning).toBe(
"This reasoning arrived after the answer stream.",
);
expect(result.current.messages[0].reasoningStreaming).toBe(false);
});
it("attaches assistant media_urls to complete messages", () => {
const fake = fakeClient();
const { result } = renderHook(() => useNanobotStream("chat-m", EMPTY_MESSAGES), {