Files
openclaw/src/shared/chat-message-content.test.ts

200 lines
5.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
extractAssistantTextForPhase,
extractAssistantVisibleText,
extractFirstTextBlock,
resolveAssistantMessagePhase,
} from "./chat-message-content.js";
describe("shared/chat-message-content", () => {
it("extracts the first text block from array content", () => {
expect(
extractFirstTextBlock({
content: [{ text: "hello" }, { text: "world" }],
}),
).toBe("hello");
});
it("returns plain string content", () => {
expect(
extractFirstTextBlock({
content: "hello from string content",
}),
).toBe("hello from string content");
});
it("preserves empty-string text in the first block", () => {
expect(
extractFirstTextBlock({
content: [{ text: "" }, { text: "later" }],
}),
).toBe("");
});
it("only considers the first content block even if later blocks have text", () => {
expect(
extractFirstTextBlock({
content: [null, { text: "later" }],
}),
).toBeUndefined();
expect(
extractFirstTextBlock({
content: [{ type: "image" }, { text: "later" }],
}),
).toBeUndefined();
});
it("returns undefined for missing, empty, or non-text content", () => {
expect(extractFirstTextBlock(null)).toBeUndefined();
expect(extractFirstTextBlock({ content: [] })).toBeUndefined();
expect(extractFirstTextBlock({ content: [{ type: "image" }] })).toBeUndefined();
expect(extractFirstTextBlock({ content: ["hello"] })).toBeUndefined();
expect(extractFirstTextBlock({ content: [{ text: 1 }, { text: "later" }] })).toBeUndefined();
});
});
describe("extractAssistantVisibleText", () => {
it("preserves boundary spacing when joining adjacent final_answer text blocks", () => {
expect(
extractAssistantTextForPhase(
{
role: "assistant",
content: [
{
type: "text",
text: "Hi ",
textSignature: JSON.stringify({ v: 1, id: "msg_final_1", phase: "final_answer" }),
},
{
type: "text",
text: "there",
textSignature: JSON.stringify({ v: 1, id: "msg_final_2", phase: "final_answer" }),
},
],
},
{ phase: "final_answer", joinWith: "" },
),
).toBe("Hi there");
});
it("prefers final_answer text over commentary text", () => {
expect(
extractAssistantVisibleText({
role: "assistant",
content: [
{
type: "text",
text: "thinking like caveman",
textSignature: JSON.stringify({ v: 1, id: "msg_commentary", phase: "commentary" }),
},
{
type: "text",
text: "Actual final answer",
textSignature: JSON.stringify({ v: 1, id: "msg_final", phase: "final_answer" }),
},
],
}),
).toBe("Actual final answer");
});
it("does not fall back to commentary-only text", () => {
expect(
extractAssistantVisibleText({
role: "assistant",
content: [
{
type: "text",
text: "thinking like caveman",
textSignature: JSON.stringify({ v: 1, id: "msg_commentary", phase: "commentary" }),
},
],
}),
).toBeUndefined();
});
it("does not fall back to unphased legacy text when final_answer is empty", () => {
expect(
extractAssistantVisibleText({
role: "assistant",
content: [
{ type: "text", text: "Legacy answer" },
{
type: "text",
text: " ",
textSignature: JSON.stringify({ v: 1, id: "msg_final", phase: "final_answer" }),
},
],
}),
).toBeUndefined();
});
it("falls back to unphased legacy text", () => {
expect(
extractAssistantVisibleText({
role: "assistant",
content: [{ type: "text", text: "Legacy answer" }],
}),
).toBe("Legacy answer");
});
it("does not mix unphased legacy text into final_answer output", () => {
expect(
extractAssistantVisibleText({
role: "assistant",
phase: "final_answer",
content: [
{ type: "text", text: "Legacy answer" },
{
type: "text",
text: "Actual final answer",
textSignature: JSON.stringify({ v: 1, id: "msg_final", phase: "final_answer" }),
},
],
}),
).toBe("Actual final answer");
});
});
describe("resolveAssistantMessagePhase", () => {
it("prefers the top-level assistant phase when present", () => {
expect(resolveAssistantMessagePhase({ role: "assistant", phase: "commentary" })).toBe(
"commentary",
);
});
it("resolves a single explicit phase from textSignature metadata", () => {
expect(
resolveAssistantMessagePhase({
role: "assistant",
content: [
{
type: "text",
text: "Actual final answer",
textSignature: JSON.stringify({ v: 1, id: "msg_final", phase: "final_answer" }),
},
],
}),
).toBe("final_answer");
});
it("returns undefined when text blocks contain mixed explicit phases", () => {
expect(
resolveAssistantMessagePhase({
role: "assistant",
content: [
{
type: "text",
text: "Working...",
textSignature: JSON.stringify({ v: 1, id: "msg_commentary", phase: "commentary" }),
},
{
type: "text",
text: "Done.",
textSignature: JSON.stringify({ v: 1, id: "msg_final", phase: "final_answer" }),
},
],
}),
).toBeUndefined();
});
});