import type { AssistantMessage } from "@mariozechner/pi-ai"; import { describe, expect, it } from "vitest"; import { extractAssistantText, formatReasoningMessage, stripDowngradedToolCallText, } from "./pi-embedded-utils.js"; describe("extractAssistantText", () => { it("strips Minimax tool invocation XML from text", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: ` netstat -tlnp | grep 18789 `, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe(""); }); it("strips multiple tool invocations", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `Let me check that. /home/admin/test.txt `, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Let me check that."); }); it("keeps invoke snippets without Minimax markers", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `Example:\n\nls\n`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe( `Example:\n\nls\n`, ); }); it("preserves normal text without tool invocations", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "This is a normal response without any tool calls.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("This is a normal response without any tool calls."); }); it("sanitizes HTTP-ish error text only when stopReason is error", () => { const msg: AssistantMessage = { role: "assistant", stopReason: "error", errorMessage: "500 Internal Server Error", content: [{ type: "text", text: "500 Internal Server Error" }], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("HTTP 500: Internal Server Error"); }); it("does not rewrite normal text that references billing plans", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Firebase downgraded Chore Champ to the Spark plan; confirm whether billing should be re-enabled.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe( "Firebase downgraded Chore Champ to the Spark plan; confirm whether billing should be re-enabled.", ); }); it("strips Minimax tool invocations with extra attributes", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `Before\nls\n\nAfter`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Before\nAfter"); }); it("strips minimax tool_call open and close tags", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "StartInnerEnd", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("StartInnerEnd"); }); it("ignores invoke blocks without minimax markers", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "BeforeKeepAfter", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("BeforeKeepAfter"); }); it("strips invoke blocks when minimax markers are present elsewhere", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "BeforeDropAfter", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("BeforeAfter"); }); it("strips invoke blocks with nested tags", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `A1B`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("AB"); }); it("strips tool XML mixed with regular content", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `I'll help you with that. ls -la Here are the results.`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("I'll help you with that.\nHere are the results."); }); it("handles multiple invoke blocks in one message", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `First check. file1.txt Second check. pwd Done.`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("First check.\nSecond check.\nDone."); }); it("handles stray closing tags without opening tags", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Some text here.More text.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Some text here.More text."); }); it("returns empty string when message is only tool invocations", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: ` test `, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe(""); }); it("handles multiple text blocks", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "First block.", }, { type: "text", text: ` ls `, }, { type: "text", text: "Third block.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("First block.\nThird block."); }); it("strips downgraded Gemini tool call text representations", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `[Tool Call: exec (ID: toolu_vrtx_014w1P6B6w4V92v4VzG7Qk12)] Arguments: { "command": "git status", "timeout": 120000 }`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe(""); }); it("strips multiple downgraded tool calls", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `[Tool Call: read (ID: toolu_1)] Arguments: { "path": "/some/file.txt" } [Tool Call: exec (ID: toolu_2)] Arguments: { "command": "ls -la" }`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe(""); }); it("strips tool results for downgraded calls", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `[Tool Result for ID toolu_123] {"status": "ok", "data": "some result"}`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe(""); }); it("preserves text around downgraded tool calls", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `Let me check that for you. [Tool Call: browser (ID: toolu_abc)] Arguments: { "action": "act", "request": "click button" }`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Let me check that for you."); }); it("preserves trailing text after downgraded tool call blocks", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `Intro text. [Tool Call: read (ID: toolu_1)] Arguments: { "path": "/tmp/file.txt" } Back to the user.`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Intro text.\nBack to the user."); }); it("handles multiple text blocks with tool calls and results", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Here's what I found:", }, { type: "text", text: `[Tool Call: read (ID: toolu_1)] Arguments: { "path": "/test.txt" }`, }, { type: "text", text: `[Tool Result for ID toolu_1] File contents here`, }, { type: "text", text: "Done checking.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Here's what I found:\nDone checking."); }); it("strips thinking tags from text content", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "El usuario quiere retomar una tarea...Aquí está tu respuesta.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Aquí está tu respuesta."); }); it("strips thinking tags with attributes", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: `HiddenVisible`, }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Visible"); }); it("strips thinking tags without closing tag", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Pensando sobre el problema...", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe(""); }); it("strips thinking tags with various formats", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Beforeinternal reasoningAfter", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("BeforeAfter"); }); it("strips antthinking tags", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Some reasoningThe actual answer.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("The actual answer."); }); it("strips final tags while keeping content", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "\nAnswer\n", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Answer"); }); it("strips thought tags", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Internal deliberationFinal response.", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("Final response."); }); it("handles nested or multiple thinking blocks", () => { const msg: AssistantMessage = { role: "assistant", content: [ { type: "text", text: "Startfirst thoughtMiddlesecond thoughtEnd", }, ], timestamp: Date.now(), }; const result = extractAssistantText(msg); expect(result).toBe("StartMiddleEnd"); }); }); describe("formatReasoningMessage", () => { it("returns empty string for empty input", () => { expect(formatReasoningMessage("")).toBe(""); }); it("returns empty string for whitespace-only input", () => { expect(formatReasoningMessage(" \n \t ")).toBe(""); }); it("wraps single line in italics", () => { expect(formatReasoningMessage("Single line of reasoning")).toBe( "Reasoning:\n_Single line of reasoning_", ); }); it("wraps each line separately for multiline text (Telegram fix)", () => { expect(formatReasoningMessage("Line one\nLine two\nLine three")).toBe( "Reasoning:\n_Line one_\n_Line two_\n_Line three_", ); }); it("preserves empty lines between reasoning text", () => { expect(formatReasoningMessage("First block\n\nSecond block")).toBe( "Reasoning:\n_First block_\n\n_Second block_", ); }); it("handles mixed empty and non-empty lines", () => { expect(formatReasoningMessage("A\n\nB\nC")).toBe("Reasoning:\n_A_\n\n_B_\n_C_"); }); it("trims leading/trailing whitespace", () => { expect(formatReasoningMessage(" \n Reasoning here \n ")).toBe( "Reasoning:\n_Reasoning here_", ); }); }); describe("stripDowngradedToolCallText", () => { it("strips [Historical context: ...] blocks", () => { const text = `[Historical context: a different model called tool "exec" with arguments {"command":"git status"}]`; expect(stripDowngradedToolCallText(text)).toBe(""); }); it("preserves text before [Historical context: ...] blocks", () => { const text = `Here is the answer.\n[Historical context: a different model called tool "read"]`; expect(stripDowngradedToolCallText(text)).toBe("Here is the answer."); }); it("preserves text around [Historical context: ...] blocks", () => { const text = `Before.\n[Historical context: tool call info]\nAfter.`; expect(stripDowngradedToolCallText(text)).toBe("Before.\nAfter."); }); it("strips multiple [Historical context: ...] blocks", () => { const text = `[Historical context: first tool call]\n[Historical context: second tool call]`; expect(stripDowngradedToolCallText(text)).toBe(""); }); it("strips mixed [Tool Call: ...] and [Historical context: ...] blocks", () => { const text = `Intro.\n[Tool Call: exec (ID: toolu_1)]\nArguments: { "command": "ls" }\n[Historical context: a different model called tool "read"]`; expect(stripDowngradedToolCallText(text)).toBe("Intro."); }); it("returns text unchanged when no markers are present", () => { const text = "Just a normal response with no markers."; expect(stripDowngradedToolCallText(text)).toBe("Just a normal response with no markers."); }); it("returns empty string for empty input", () => { expect(stripDowngradedToolCallText("")).toBe(""); }); });