diff --git a/src/client/src/chatMessages.test.ts b/src/client/src/chatMessages.test.ts index 387dd59..c446cbb 100644 --- a/src/client/src/chatMessages.test.ts +++ b/src/client/src/chatMessages.test.ts @@ -13,6 +13,13 @@ describe("chat message normalization", () => { ]); }); + it("preserves already-normalized chat lines", () => { + const line = { role: "assistant" as const, parts: [{ type: "text" as const, text: "cached" }] }; + + expect(normalizeMessage(line)).toEqual([line]); + expect(normalizeMessages([{ role: "user", content: "raw" }, line])).toEqual([textMessage("user", "raw"), line]); + }); + it("normalizes tool calls and tool results", () => { expect(normalizeMessage({ role: "assistant", content: [{ type: "toolCall", name: "bash", arguments: { command: "npm test" } }] })).toEqual([ { role: "assistant", parts: [{ type: "toolCall", toolName: "bash", summary: "npm test" }] }, diff --git a/src/client/src/chatMessages.ts b/src/client/src/chatMessages.ts index 927ea69..3ac1066 100644 --- a/src/client/src/chatMessages.ts +++ b/src/client/src/chatMessages.ts @@ -42,6 +42,7 @@ export function appendThinking(messages: ChatLine[], text: string): ChatLine[] { } export function normalizeMessage(message: unknown): ChatLine[] { + if (isChatLine(message)) return [message]; if (getString(message, "role") === "bashExecution") return [withMessageMeta(normalizeBashExecution(message), message)]; const role = normalizeRole(getString(message, "role")); const parts = normalizeContent(getProperty(message, "content"), message); @@ -55,6 +56,12 @@ export function normalizeMessage(message: unknown): ChatLine[] { return visible.length > 0 ? [withMessageMeta({ role: displayRole, parts: visible, ...(source === undefined ? {} : { source }) }, message)] : []; } +function isChatLine(message: unknown): message is ChatLine { + const role = getString(message, "role"); + return (role === "user" || role === "assistant" || role === "tool" || role === "system" || role === "bash" || role === "skill") + && Array.isArray(getProperty(message, "parts")); +} + function normalizeSkillInvocation(parts: ChatPart[]): ChatLine[] | undefined { if (parts.length !== 1 || parts[0]?.type !== "text") return undefined; const skill = parseSkillBlock(parts[0].text);