import { describe, expect, test } from "bun:test";
import { stripSpotlightInjections } from "../context/strip-injections.js";
import { stripExistingMemoryInjections } from "../plugins/defaults/memory/graph/conversation-graph-memory.js";
import { wrapMemorySpotlightBlock } from "../plugins/defaults/memory/memory-marker.js";
import type { ContentBlock, Message } from "../providers/types.js";
// ---------------------------------------------------------------------------
// stripExistingMemoryInjections — removes memory-injected blocks from the
// front of the last user message while preserving user-attached content.
// ---------------------------------------------------------------------------
function userMsg(...content: ContentBlock[]): Message {
return { role: "user", content };
}
function assistantMsg(text: string): Message {
return { role: "assistant", content: [{ type: "text", text }] };
}
const textBlock = (text: string): ContentBlock => ({ type: "text", text });
const imageBlock: ContentBlock = {
type: "image",
source: { type: "base64", media_type: "image/png", data: "iVBORw0KGgo=" },
};
const memoryTextBlock: ContentBlock = {
type: "text",
text: "\nSome recalled context\n",
};
const memoryImageMarker: ContentBlock = {
type: "text",
text: "\nA photo of a sunset",
};
const memoryImageClose: ContentBlock = {
type: "text",
text: "",
};
// Legacy 2-block format (persisted in older conversations)
const legacyMemoryImageMarker: ContentBlock = {
type: "text",
text: "A photo of a sunset",
};
const memoryImage: ContentBlock = {
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: "/9j/4AAQ==",
},
};
describe("stripExistingMemoryInjections", () => {
test("no-op when content has no memory blocks", () => {
const messages = [userMsg(textBlock("hello"), imageBlock)];
const result = stripExistingMemoryInjections(messages);
expect(result).toEqual(messages);
});
test("no-op for empty messages array", () => {
const result = stripExistingMemoryInjections([]);
expect(result).toEqual([]);
});
test("no-op when last message is assistant role", () => {
const messages = [userMsg(textBlock("hi")), assistantMsg("hey")];
const result = stripExistingMemoryInjections(messages);
expect(result).toEqual(messages);
});
test("strips memory text block", () => {
const messages = [userMsg(memoryTextBlock, textBlock("hello"))];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([textBlock("hello")]);
});
test("strips 3-block memory image (marker + image + close)", () => {
const messages = [
userMsg(
memoryTextBlock,
memoryImageMarker,
memoryImage,
memoryImageClose,
textBlock("hi"),
),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([textBlock("hi")]);
});
test("strips multiple 3-block memory image groups", () => {
const messages = [
userMsg(
memoryTextBlock,
memoryImageMarker,
memoryImage,
memoryImageClose,
memoryImageMarker,
memoryImage,
memoryImageClose,
textBlock("hello"),
),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([textBlock("hello")]);
});
test("strips legacy 2-block memory image (no closing tag)", () => {
const messages = [
userMsg(
memoryTextBlock,
legacyMemoryImageMarker,
memoryImage,
textBlock("hi"),
),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([textBlock("hi")]);
});
test("preserves user-attached image when it is the only content", () => {
const messages = [userMsg(imageBlock)];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([imageBlock]);
});
test("preserves user-attached image with text", () => {
const messages = [userMsg(imageBlock, textBlock("what is this?"))];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([imageBlock, textBlock("what is this?")]);
});
test("preserves user image after stripping 3-block memory blocks", () => {
const messages = [
userMsg(
memoryTextBlock,
memoryImageMarker,
memoryImage,
memoryImageClose,
imageBlock,
textBlock("look at this"),
),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([imageBlock, textBlock("look at this")]);
});
test("preserves user image-only message after stripping memory blocks", () => {
const messages = [userMsg(memoryTextBlock, imageBlock)];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([imageBlock]);
});
test("does not modify earlier messages", () => {
const earlier = userMsg(textBlock("first"));
const messages = [
earlier,
assistantMsg("ok"),
userMsg(memoryTextBlock, textBlock("second")),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0]).toBe(earlier);
expect(result[2].content).toEqual([textBlock("second")]);
});
test("does not strip user text that equals ", () => {
const messages = [userMsg(textBlock(""))];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([textBlock("")]);
});
test("does not strip after memory text block (no image context)", () => {
const messages = [
userMsg(
memoryTextBlock,
textBlock(""),
textBlock("hello"),
),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([
textBlock(""),
textBlock("hello"),
]);
});
test("strips images-first then text (actual injectMemoryBlock order)", () => {
const messages = [
userMsg(
memoryImageMarker,
memoryImage,
memoryImageClose,
memoryTextBlock,
textBlock("hello"),
),
];
const result = stripExistingMemoryInjections(messages);
expect(result[0].content).toEqual([textBlock("hello")]);
});
// Regression guard: the helper must remain last-message-only — memory-v3's
// frozen-card carry depends on historical `` blocks staying intact.
test("strips ONLY the last user message, leaving earlier ones injected", () => {
const messages = [
userMsg(memoryTextBlock, textBlock("first")),
assistantMsg("ok"),
userMsg(memoryTextBlock, textBlock("second")),
];
const result = stripExistingMemoryInjections(messages);
// Earlier user message keeps its injected memory block.
expect(result[0].content).toEqual([memoryTextBlock, textBlock("first")]);
// Last user message is stripped.
expect(result[2].content).toEqual([textBlock("second")]);
});
});
// ---------------------------------------------------------------------------
// stripSpotlightInjections — the memory-v3 ephemeral spotlight is the ONLY
// block strip-and-replaced across every user message each turn. Frozen
// `` card blocks must never be touched by it (the cache contract);
// the old whole-layer stripAllMemoryInjections replace is gone.
// ---------------------------------------------------------------------------
const spotlightBlock = (inner: string): ContentBlock => ({
type: "text",
text: wrapMemorySpotlightBlock(inner),
});
describe("stripSpotlightInjections", () => {
test("strips spotlight blocks from every user message, leaving blocks intact", () => {
const messages = [
userMsg(memoryTextBlock, textBlock("first"), spotlightBlock("s1")),
assistantMsg("ok"),
userMsg(memoryTextBlock, textBlock("second"), spotlightBlock("s2")),
];
const result = stripSpotlightInjections(messages);
expect(result[0].content).toEqual([memoryTextBlock, textBlock("first")]);
expect(result[1]).toBe(messages[1]); // assistant message untouched
expect(result[2].content).toEqual([memoryTextBlock, textBlock("second")]);
});
test("requires the full wrapper: user text merely starting with the tag survives", () => {
const lookalike = textBlock("\nnot really a block");
const messages = [userMsg(lookalike, textBlock("hello"))];
const result = stripSpotlightInjections(messages);
expect(result[0].content).toEqual([lookalike, textBlock("hello")]);
});
test("content no-op when no spotlight blocks exist", () => {
const messages = [
userMsg(memoryTextBlock, textBlock("hello")),
assistantMsg("hi"),
userMsg(imageBlock),
];
const result = stripSpotlightInjections(messages);
expect(result).toEqual(messages);
// Untouched messages keep their identity (cheap no-op detection upstream).
expect(result[0]).toBe(messages[0]);
});
test("no-op for empty messages array", () => {
expect(stripSpotlightInjections([])).toEqual([]);
});
});