diff --git a/site/src/pages/AgentsPage/AgentDetail/messageParsing.test.ts b/site/src/pages/AgentsPage/AgentDetail/messageParsing.test.ts index e764d8b16d..30c3b90a3c 100644 --- a/site/src/pages/AgentsPage/AgentDetail/messageParsing.test.ts +++ b/site/src/pages/AgentsPage/AgentDetail/messageParsing.test.ts @@ -101,11 +101,28 @@ describe("parseMessageContent", () => { { type: "text", text: "Line one" }, { type: "text", text: "Line two" }, ]); - expect(result.markdown).toBe("Line one\nLine two"); + expect(result.markdown).toBe("Line oneLine two"); expect(result.blocks).toHaveLength(1); expect(result.blocks[0]).toEqual({ type: "response", - text: "Line one\nLine two", + text: "Line oneLine two", + }); + }); + + it("normalizes list markers split across text blocks", () => { + // LLMs stream list markers and item content as separate text + // blocks. Both paths (streaming and completed) must concatenate + // them directly so the marker and content stay on the same line. + const result = parseMessageContent([ + { type: "text", text: "Intro\n\n- " }, + { type: "text", text: "First item" }, + { type: "text", text: "\n- " }, + { type: "text", text: "Second item" }, + ]); + expect(result.blocks).toHaveLength(1); + expect(result.blocks[0]).toEqual({ + type: "response", + text: "Intro\n\n- First item\n- Second item", }); }); diff --git a/site/src/pages/AgentsPage/AgentDetail/messageParsing.ts b/site/src/pages/AgentsPage/AgentDetail/messageParsing.ts index e86aa24446..8e29b5b686 100644 --- a/site/src/pages/AgentsPage/AgentDetail/messageParsing.ts +++ b/site/src/pages/AgentsPage/AgentDetail/messageParsing.ts @@ -11,15 +11,12 @@ import type { RenderBlock, } from "./types"; +/** Concatenate text chunks, skipping whitespace-only values. */ const appendText = (current: string, next: string): string => { - const trimmed = next.trim(); - if (!trimmed) { + if (!next.trim()) { return current; } - if (!current) { - return next; - } - return `${current}\n${next}`; + return `${current}${next}`; }; export const asOptionalTitle = (value: unknown): string | undefined => @@ -79,13 +76,14 @@ const emptyParsedMessageContent = (): ParsedMessageContent => ({ sources: [], }); -/** Wraps appendTextBlock with newline-joining for complete message blocks. */ +/** Wraps appendTextBlock using the same direct concatenation as + * the streaming path so both produce identical markdown. */ const appendParsedTextBlock = ( blocks: RenderBlock[], type: "response" | "thinking", text: string, title?: string, -): RenderBlock[] => appendTextBlock(blocks, type, text, title, appendText); +): RenderBlock[] => appendTextBlock(blocks, type, text, title); export const ensureToolBlock = ( blocks: RenderBlock[],