import type { Message } from "@langchain/langgraph-sdk"; import { expect, test } from "vitest"; import { getVisibleOptimisticMessages, mergeMessages, } from "@/core/threads/hooks"; test("mergeMessages removes duplicate messages already present in history", () => { const human = { id: "human-1", type: "human", content: "Design an agent", } as Message; const ai = { id: "ai-1", type: "ai", content: "Let's design it.", } as Message; expect(mergeMessages([human, ai, human, ai], [], [])).toEqual([human, ai]); }); test("mergeMessages lets live thread messages replace overlapping history", () => { const oldHuman = { id: "human-1", type: "human", content: "old", } as Message; const liveHuman = { id: "human-1", type: "human", content: "live", } as Message; const oldAi = { id: "ai-1", type: "ai", content: "old", } as Message; const liveAi = { id: "ai-1", type: "ai", content: "live", } as Message; expect(mergeMessages([oldHuman, oldAi], [liveHuman, liveAi], [])).toEqual([ liveHuman, liveAi, ]); }); test("mergeMessages deduplicates tool messages by tool_call_id", () => { const oldTool = { id: "tool-message-old", type: "tool", tool_call_id: "call-1", content: "old", } as Message; const liveTool = { id: "tool-message-live", type: "tool", tool_call_id: "call-1", content: "live", } as Message; expect(mergeMessages([oldTool], [liveTool], [])).toEqual([liveTool]); }); test("getVisibleOptimisticMessages hides optimistic user input after server human arrives", () => { const optimisticHuman = { id: "opt-human-1", type: "human", content: "hello", } as Message; expect(getVisibleOptimisticMessages([optimisticHuman], 0, 1)).toEqual([]); }); test("mergeMessages shows server human instead of optimistic duplicate after first response", () => { const serverHuman = { id: "server-human-1", type: "human", content: "hello", } as Message; const optimisticHuman = { id: "opt-human-1", type: "human", content: "hello", } as Message; const visibleOptimistic = getVisibleOptimisticMessages( [optimisticHuman], 0, 1, ); expect(mergeMessages([], [serverHuman], visibleOptimistic)).toEqual([ serverHuman, ]); }); test("getVisibleOptimisticMessages keeps optimistic user input until server human arrives", () => { const optimisticHuman = { id: "opt-human-1", type: "human", content: "hello", } as Message; expect(getVisibleOptimisticMessages([optimisticHuman], 0, 0)).toEqual([ optimisticHuman, ]); }); test("getVisibleOptimisticMessages keeps non-human optimistic status messages", () => { const optimisticAi = { id: "opt-ai-1", type: "ai", content: "Uploading files...", } as Message; expect(getVisibleOptimisticMessages([optimisticAi], 0, 1)).toEqual([ optimisticAi, ]); }); test("getVisibleOptimisticMessages hides the upload optimistic pair after server human arrives", () => { const optimisticHuman = { id: "opt-human-1", type: "human", content: "upload this", } as Message; const optimisticUploadingAi = { id: "opt-ai-uploading", type: "ai", content: "Uploading files...", } as Message; expect( getVisibleOptimisticMessages( [optimisticHuman, optimisticUploadingAi], 0, 1, ), ).toEqual([]); }); test("getVisibleOptimisticMessages hides optimistic user input after later server turns", () => { const optimisticHuman = { id: "opt-human-2", type: "human", content: "follow up", } as Message; expect(getVisibleOptimisticMessages([optimisticHuman], 3, 4)).toEqual([]); expect(getVisibleOptimisticMessages([optimisticHuman], 3, 3)).toEqual([ optimisticHuman, ]); });