Files

131 lines
4.5 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
async function loadSubject() {
return import("./message-display.ts");
}
function assistant(content) {
return {
role: "assistant",
provider: "test",
model: "test-model",
content,
};
}
test("splits trailing final answer blocks from process blocks", async () => {
const { splitFinalAssistantBlocks } = await loadSubject();
const message = assistant([
{ type: "thinking", thinking: "work through it" },
{ type: "toolCall", toolCallId: "call-1", toolName: "bash", input: {} },
{ type: "text", text: "Final answer" },
{ type: "image", source: { type: "url", url: "https://example.com/final.png" } },
]);
const result = splitFinalAssistantBlocks(message, { isStreaming: false });
assert.deepEqual(result.answerBlocks.map((block) => block.type), ["text", "image"]);
assert.deepEqual(result.processBlocks.map((block) => block.type), ["thinking", "toolCall"]);
});
test("keeps pre-tool text in process blocks", async () => {
const { splitFinalAssistantBlocks } = await loadSubject();
const message = assistant([
{ type: "text", text: "I will inspect the repo first." },
{ type: "toolCall", toolCallId: "call-1", toolName: "bash", input: {} },
{ type: "text", text: "Final answer" },
]);
const result = splitFinalAssistantBlocks(message, { isStreaming: false });
assert.deepEqual(result.answerBlocks.map((block) => block.type), ["text"]);
assert.equal(result.answerBlocks[0].text, "Final answer");
assert.deepEqual(result.processBlocks.map((block) => block.type), ["text", "toolCall"]);
});
test("does not expose text before a trailing tool call as final answer", async () => {
const { splitFinalAssistantBlocks } = await loadSubject();
const message = assistant([
{ type: "thinking", thinking: "work through it" },
{ type: "text", text: "I need to call a tool." },
{ type: "toolCall", toolCallId: "call-1", toolName: "bash", input: {} },
]);
const result = splitFinalAssistantBlocks(message, { isStreaming: false });
assert.deepEqual(result.answerBlocks, []);
assert.deepEqual(result.processBlocks.map((block) => block.type), ["thinking", "text", "toolCall"]);
});
test("drops empty thinking blocks after completion", async () => {
const { getDisplayableAssistantBlocks, splitFinalAssistantBlocks } = await loadSubject();
const message = assistant([
{ type: "thinking", thinking: "" },
{ type: "text", text: "Final answer" },
]);
assert.deepEqual(
getDisplayableAssistantBlocks(message, { isStreaming: false }).map((block) => block.type),
["text"],
);
const result = splitFinalAssistantBlocks(message, { isStreaming: false });
assert.deepEqual(result.answerBlocks.map((block) => block.type), ["text"]);
assert.deepEqual(result.processBlocks, []);
});
test("keeps empty thinking while streaming", async () => {
const { splitFinalAssistantBlocks } = await loadSubject();
const message = assistant([
{ type: "thinking", thinking: "" },
{ type: "text", text: "Partial answer" },
]);
const result = splitFinalAssistantBlocks(message, { isStreaming: true });
assert.deepEqual(result.answerBlocks.map((block) => block.type), ["text"]);
assert.deepEqual(result.processBlocks.map((block) => block.type), ["thinking"]);
});
test("keeps deferred historical thinking placeholders", async () => {
const { getDisplayableAssistantBlocks } = await loadSubject();
const message = assistant([
{ type: "thinking", thinking: "", deferred: true },
{ type: "text", text: "Final answer" },
]);
assert.deepEqual(
getDisplayableAssistantBlocks(message, { isStreaming: false }).map((block) => block.type),
["thinking", "text"],
);
});
test("returns completed provider errors even when the message has no content", async () => {
const { getAssistantErrorMessage } = await loadSubject();
const message = {
...assistant([]),
stopReason: "error",
errorMessage: "OpenAI API error (403): request forbidden",
};
assert.equal(
getAssistantErrorMessage(message),
"OpenAI API error (403): request forbidden",
);
assert.equal(getAssistantErrorMessage(message, { isStreaming: true }), null);
});
test("falls back when a provider error has no message", async () => {
const { getAssistantErrorMessage } = await loadSubject();
assert.equal(
getAssistantErrorMessage({ ...assistant([]), stopReason: "error" }),
"Unknown provider error",
);
assert.equal(
getAssistantErrorMessage({ ...assistant([]), stopReason: "stop" }),
null,
);
});