import type { Message } from "@langchain/langgraph-sdk"; import { expect, test } from "vitest"; import { getSummarizationMiddlewareMessages, 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("mergeMessages keeps a visible history message when a hidden live message reuses its id", () => { const historyHuman = { id: "human-1", type: "human", content: "visible user prompt", } as Message; const hiddenReminder = { id: "human-1", type: "human", content: "hidden", additional_kwargs: { hide_from_ui: true }, } as Message; const liveAi = { id: "ai-1", type: "ai", content: "live answer", } as Message; expect(mergeMessages([historyHuman], [hiddenReminder, liveAi], [])).toEqual([ historyHuman, liveAi, ]); }); test("mergeMessages lets a visible live message replace overlapping hidden history", () => { const hiddenHistoryHuman = { id: "human-1", type: "human", content: "hidden", additional_kwargs: { hide_from_ui: true }, } as Message; const liveHuman = { id: "human-1", type: "human", content: "visible user prompt", } as Message; expect(mergeMessages([hiddenHistoryHuman], [liveHuman], [])).toEqual([ liveHuman, ]); }); test("getSummarizationMiddlewareMessages matches DeerFlow summarization update keys", () => { const removeAll = { id: "__remove_all__", type: "remove", content: "", } as Message; const summary = { id: "summary-1", type: "human", name: "summary", content: "summary", } as Message; expect( getSummarizationMiddlewareMessages({ "DeerFlowSummarizationMiddleware.before_model": { messages: [removeAll, summary], }, }), ).toEqual([removeAll, summary]); }); test("getSummarizationMiddlewareMessages matches base LangChain summarization update keys", () => { const summary = { id: "summary-1", type: "human", name: "summary", content: "summary", } as Message; expect( getSummarizationMiddlewareMessages({ "SummarizationMiddleware.before_model": { messages: [summary], }, }), ).toEqual([summary]); }); test("getSummarizationMiddlewareMessages ignores unrelated suffix-sharing update keys", () => { const summary = { id: "summary-1", type: "human", name: "summary", content: "summary", } as Message; expect( getSummarizationMiddlewareMessages({ "OtherSummarizationMiddleware.before_model": { messages: [summary], }, }), ).toBeUndefined(); }); 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, ]); });