/** * KHAELOR * File: tests/anthropic/caching.test.ts * Description: Cache breakpoint planning tests — tier-end breakpoints, last-user-message breakpoints, 4-breakpoint budget. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { describe, expect, it } from "vitest"; import { MAX_CACHE_BREAKPOINTS, buildMessagesWithCacheControl, buildSystemBlocks, planCacheBreakpoints, } from "../../src/anthropic/caching.js"; import type { AnthropicMessage, SystemTier } from "../../src/anthropic/types.js"; const TIERS: SystemTier[] = [ { name: "identity", text: "You are KHAELOR." }, { name: "tool-guidance", text: "Use tools carefully." }, { name: "project-instructions", text: "Follow KHAELOR.md." }, ]; function user(text: string): AnthropicMessage { return { role: "user", content: [{ type: "text", text }] }; } function assistant(text: string): AnthropicMessage { return { role: "assistant", content: [{ type: "text", text }] }; } describe("planCacheBreakpoints", () => { it("marks the first and last system tier ends, plus the last two user messages", () => { const messages = [user("a"), assistant("b"), user("c"), assistant("d"), user("e")]; const plan = planCacheBreakpoints({ system: TIERS, messages }); expect(plan.systemTierIndices).toEqual([0, 2]); expect(plan.messageIndices).toEqual([2, 4]); expect(plan.systemTierIndices.length + plan.messageIndices.length).toBeLessThanOrEqual( MAX_CACHE_BREAKPOINTS, ); }); it("uses a single breakpoint for a single system tier", () => { const plan = planCacheBreakpoints({ system: [TIERS[0] as SystemTier], messages: [user("a")] }); expect(plan.systemTierIndices).toEqual([0]); expect(plan.messageIndices).toEqual([0]); }); it("handles empty system and empty messages", () => { const plan = planCacheBreakpoints({ system: [], messages: [] }); expect(plan.systemTierIndices).toEqual([]); expect(plan.messageIndices).toEqual([]); }); it("is deterministic — same input shape produces the same plan", () => { const messages = [user("a"), user("b"), user("c")]; const first = planCacheBreakpoints({ system: TIERS, messages }); const second = planCacheBreakpoints({ system: TIERS, messages }); expect(second).toEqual(first); }); }); describe("buildSystemBlocks", () => { it("renders tiers verbatim with cache_control only at planned tier ends", () => { const plan = planCacheBreakpoints({ system: TIERS, messages: [] }); const blocks = buildSystemBlocks(TIERS, plan); expect(blocks.map((b) => b.text)).toEqual(TIERS.map((t) => t.text)); // byte-stable expect(blocks[0]?.cache_control).toEqual({ type: "ephemeral" }); expect(blocks[1]?.cache_control).toBeUndefined(); expect(blocks[2]?.cache_control).toEqual({ type: "ephemeral" }); }); }); describe("buildMessagesWithCacheControl", () => { it("marks the last cacheable block of planned messages without mutating the input", () => { const messages = [user("a"), assistant("b"), user("c")]; const plan = planCacheBreakpoints({ system: [], messages }); const rendered = buildMessagesWithCacheControl(messages, plan); const last = rendered[2]?.content[0]; expect(last).toMatchObject({ type: "text", cache_control: { type: "ephemeral" } }); // Input history untouched (byte-stability of the projection). expect(messages[2]?.content[0]).toEqual({ type: "text", text: "c" }); // Unmarked messages pass through by reference. expect(rendered[1]).toBe(messages[1]); }); it("places the breakpoint on tool_result blocks (the common tool-loop tail)", () => { const messages: AnthropicMessage[] = [ user("start"), { role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "ok" }], }, ]; const plan = planCacheBreakpoints({ system: [], messages }); const rendered = buildMessagesWithCacheControl(messages, plan); expect(rendered[1]?.content[0]).toMatchObject({ type: "tool_result", cache_control: { type: "ephemeral" }, }); }); it("skips non-cacheable blocks (thinking) when choosing the breakpoint block", () => { const messages: AnthropicMessage[] = [ { role: "user", content: [ { type: "text", text: "before" }, { type: "thinking", thinking: "t", signature: "s" }, ], }, ]; const plan = planCacheBreakpoints({ system: [], messages }); const rendered = buildMessagesWithCacheControl(messages, plan); expect(rendered[0]?.content[0]).toMatchObject({ type: "text", cache_control: { type: "ephemeral" }, }); expect(rendered[0]?.content[1]).not.toHaveProperty("cache_control"); }); });