108 lines
4.0 KiB
TypeScript
108 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { parseAssistantMessageSections, parseContentWithToolCalls } from "./message-sections";
|
|
|
|
describe("parseAssistantMessageSections", () => {
|
|
it("returns plain assistant content when there is no thought block", () => {
|
|
expect(parseAssistantMessageSections("直接回答")).toEqual({
|
|
answer: "直接回答",
|
|
thought: null,
|
|
thoughtComplete: false
|
|
});
|
|
});
|
|
|
|
it("extracts a completed thought block and keeps the final answer visible", () => {
|
|
expect(parseAssistantMessageSections("<think>先分析需求</think>\n\n最终回答")).toEqual({
|
|
answer: "最终回答",
|
|
thought: "先分析需求",
|
|
thoughtComplete: true
|
|
});
|
|
});
|
|
|
|
it("supports streaming thought content before the closing tag arrives", () => {
|
|
expect(parseAssistantMessageSections("准备中...\n<think>继续推理中")).toEqual({
|
|
answer: "准备中...",
|
|
thought: "继续推理中",
|
|
thoughtComplete: false
|
|
});
|
|
});
|
|
|
|
it("merges multiple thought blocks into a single collapsed section", () => {
|
|
expect(
|
|
parseAssistantMessageSections(
|
|
"<think>第一段思考</think>\n答案开头\n<think>第二段思考</think>\n答案结尾"
|
|
)
|
|
).toEqual({
|
|
answer: "答案开头\n\n答案结尾",
|
|
thought: "第一段思考\n\n第二段思考",
|
|
thoughtComplete: true
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("parseContentWithToolCalls", () => {
|
|
it("returns a single text segment when there are no tool calls", () => {
|
|
const result = parseContentWithToolCalls("普通文本回答");
|
|
|
|
expect(result.segments).toEqual([{ type: "text", content: "普通文本回答" }]);
|
|
expect(result.toolCalls).toHaveLength(0);
|
|
});
|
|
|
|
it("parses a complete tool_call block", () => {
|
|
const content =
|
|
'分析完成。\n<tool_call>{"tool":"locate_junctions","params":{"ids":["J1","J2"]}}</tool_call>\n以上是结果。';
|
|
const result = parseContentWithToolCalls(content);
|
|
|
|
expect(result.toolCalls).toHaveLength(1);
|
|
expect(result.toolCalls[0].tool).toBe("locate_junctions");
|
|
expect(result.toolCalls[0].params).toEqual({ ids: ["J1", "J2"] });
|
|
expect(result.segments).toHaveLength(3);
|
|
expect(result.segments[0]).toEqual({ type: "text", content: "分析完成。" });
|
|
expect(result.segments[1]).toMatchObject({
|
|
type: "tool_call",
|
|
toolCall: { tool: "locate_junctions" }
|
|
});
|
|
expect(result.segments[2]).toEqual({ type: "text", content: "以上是结果。" });
|
|
});
|
|
|
|
it("parses multiple tool_call blocks", () => {
|
|
const content =
|
|
'文本1\n<tool_call>{"tool":"locate_pipes","params":{"ids":["P1"]}}</tool_call>\n文本2\n<tool_call>{"tool":"chart","params":{"title":"图"}}</tool_call>';
|
|
const result = parseContentWithToolCalls(content);
|
|
|
|
expect(result.toolCalls).toHaveLength(2);
|
|
expect(result.toolCalls[0].tool).toBe("locate_pipes");
|
|
expect(result.toolCalls[1].tool).toBe("chart");
|
|
expect(result.segments).toHaveLength(4);
|
|
});
|
|
|
|
it("detects an unclosed tool_call tag as pending while streaming", () => {
|
|
const result = parseContentWithToolCalls('正在分析...\n<tool_call>{"tool":"locate_no');
|
|
|
|
expect(result.segments).toEqual([
|
|
{ type: "text", content: "正在分析..." },
|
|
{ type: "tool_call_pending" }
|
|
]);
|
|
expect(result.toolCalls).toHaveLength(0);
|
|
});
|
|
|
|
it("strips partial opening tags during streaming", () => {
|
|
const result = parseContentWithToolCalls("正在分析...\n<tool_c");
|
|
|
|
expect(result.segments).toEqual([{ type: "text", content: "正在分析..." }]);
|
|
});
|
|
|
|
it("handles malformed JSON gracefully", () => {
|
|
const result = parseContentWithToolCalls("前文\n<tool_call>{invalid json}</tool_call>\n后文");
|
|
|
|
expect(result.toolCalls).toHaveLength(0);
|
|
expect(result.segments.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
|
|
it("returns empty segments for empty content", () => {
|
|
const result = parseContentWithToolCalls("");
|
|
|
|
expect(result.segments).toHaveLength(0);
|
|
expect(result.toolCalls).toHaveLength(0);
|
|
});
|
|
});
|