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([]); }); });