test(frontend/copilot): add tests for splitReasoningAndResponse and isReasoningToolPart

Covers:
- isReasoningToolPart returns true for search/reasoning tools, false for action tools
- splitReasoningAndResponse: all text (no tools) → all response
- splitReasoningAndResponse: text + reasoning tools + text → proper split
- splitReasoningAndResponse: text + action tools + text → all response (no split)
- splitReasoningAndResponse: mixed reasoning + action tools → only reasoning triggers split
- splitReasoningAndResponse: reasoning tools with no text after → all response
- splitReasoningAndResponse: action tool after response text stays visible

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lluis Agusti
2026-04-16 16:54:16 +07:00
parent 83d25bf18a
commit fdfb79530e

View File

@@ -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.",
);
});
});