diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/helpers.test.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/helpers.test.ts index 831894d57b..d44976b953 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/helpers.test.ts +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/helpers.test.ts @@ -1,5 +1,29 @@ import { describe, expect, it } from "vitest"; -import { extractWorkspaceArtifacts, filePartToArtifactRef } from "./helpers"; +import { + extractWorkspaceArtifacts, + filePartToArtifactRef, + isReasoningToolPart, + splitReasoningAndResponse, +} from "./helpers"; +import type { MessagePart } from "./helpers"; + +function textPart(text: string): MessagePart { + return { type: "text", text } as MessagePart; +} + +function toolPart( + toolName: string, + state: string = "output-available", +): MessagePart { + return { + type: `tool-${toolName}`, + state, + toolCallId: `call-${toolName}`, + toolName, + args: {}, + output: "{}", + } as unknown as MessagePart; +} describe("extractWorkspaceArtifacts", () => { it("extracts a single workspace:// link with its markdown title", () => { @@ -101,3 +125,130 @@ describe("filePartToArtifactRef", () => { expect(overridden?.origin).toBe("agent"); }); }); + +describe("isReasoningToolPart", () => { + it("returns true for reasoning/search tools", () => { + const reasoningTools = [ + "find_block", + "find_agent", + "find_library_agent", + "search_docs", + "get_doc_page", + "search_feature_requests", + "ask_question", + ]; + for (const name of reasoningTools) { + expect(isReasoningToolPart(toolPart(name))).toBe(true); + } + }); + + it("returns false for action tools", () => { + const actionTools = [ + "run_block", + "run_agent", + "create_agent", + "edit_agent", + "run_mcp_tool", + "schedule_agent", + "continue_run_block", + ]; + for (const name of actionTools) { + expect(isReasoningToolPart(toolPart(name))).toBe(false); + } + }); + + it("returns false for text parts", () => { + expect(isReasoningToolPart(textPart("hello"))).toBe(false); + }); +}); + +describe("splitReasoningAndResponse", () => { + it("returns all parts as response when there are no tools", () => { + const parts = [textPart("Hello"), textPart("World")]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toEqual([]); + expect(result.response).toEqual(parts); + }); + + it("splits on reasoning tools — text before goes to reasoning", () => { + const parts = [ + textPart("Let me search..."), + toolPart("find_block"), + textPart("Here is your answer"), + ]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toHaveLength(2); + expect(result.response).toHaveLength(1); + expect((result.response[0] as { text: string }).text).toBe( + "Here is your answer", + ); + }); + + it("does NOT split on action tools — response before run_block stays visible", () => { + const parts = [ + textPart("Here is my answer"), + toolPart("run_block"), + textPart("Block finished"), + ]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toEqual([]); + expect(result.response).toEqual(parts); + }); + + it("splits only on reasoning tools when both reasoning and action tools are present", () => { + const parts = [ + textPart("Planning..."), + toolPart("search_docs"), + textPart("Found it. Running now."), + toolPart("run_block"), + textPart("Done!"), + ]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toHaveLength(2); + expect(result.response).toHaveLength(3); + expect((result.response[0] as { text: string }).text).toBe( + "Found it. Running now.", + ); + }); + + it("returns all as response when reasoning tools have no text after them", () => { + const parts = [ + textPart("Hello"), + toolPart("find_agent"), + toolPart("run_block"), + ]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toEqual([]); + expect(result.response).toEqual(parts); + }); + + it("handles multiple reasoning tools correctly", () => { + const parts = [ + textPart("Searching..."), + toolPart("find_block"), + textPart("Found one, searching more..."), + toolPart("search_docs"), + textPart("Here are the results"), + ]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toHaveLength(4); + expect(result.response).toHaveLength(1); + expect((result.response[0] as { text: string }).text).toBe( + "Here are the results", + ); + }); + + it("handles action tool after response text without hiding the response", () => { + const parts = [ + toolPart("find_block"), + textPart("I found it! Let me run it."), + toolPart("run_agent"), + ]; + const result = splitReasoningAndResponse(parts); + expect(result.reasoning).toHaveLength(1); + expect(result.response).toHaveLength(2); + expect((result.response[0] as { text: string }).text).toBe( + "I found it! Let me run it.", + ); + }); +});