132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
import {
|
|
parseAssistantMessageSections,
|
|
parseContentWithToolCalls,
|
|
} from "./chatMessageSections";
|
|
|
|
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 (streaming)", () => {
|
|
const content = '正在分析...\n<tool_call>{"tool":"locate_no';
|
|
const result = parseContentWithToolCalls(content);
|
|
|
|
expect(result.segments).toHaveLength(2);
|
|
expect(result.segments[0]).toEqual({
|
|
type: "text",
|
|
content: "正在分析...",
|
|
});
|
|
expect(result.segments[1]).toEqual({ type: "tool_call_pending" });
|
|
expect(result.toolCalls).toHaveLength(0);
|
|
});
|
|
|
|
it("strips partial opening tags during streaming", () => {
|
|
const content = "正在分析...\n<tool_c";
|
|
const result = parseContentWithToolCalls(content);
|
|
|
|
expect(result.segments).toHaveLength(1);
|
|
expect(result.segments[0]).toEqual({
|
|
type: "text",
|
|
content: "正在分析...",
|
|
});
|
|
});
|
|
|
|
it("handles malformed JSON gracefully", () => {
|
|
const content =
|
|
'前文\n<tool_call>{invalid json}</tool_call>\n后文';
|
|
const result = parseContentWithToolCalls(content);
|
|
|
|
// Malformed tool call is treated as text
|
|
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);
|
|
});
|
|
});
|