/** * affinity.test.ts — transcript-prefix session matching rules. */ import { describe, it, expect } from "vitest"; import { matchSessionPrefix, normalizeAssistantContent, trimToRespondable, type AffinityCandidate, } from "../src/openai/affinity.js"; import type { ChatMessage } from "@marketplace/shared/tx"; const u = (content: string): ChatMessage => ({ role: "user", content }); const a = (content: string): ChatMessage => ({ role: "assistant", content }); const TOOL_CALL = { id: "call_1", type: "function" as const, function: { name: "get_time", arguments: "{}" } }; function cand(sessionId: string, mirror: ChatMessage[], lastUsedAt = 0, model = "m"): AffinityCandidate { return { sessionId, model, mirror, lastUsedAt }; } describe("matchSessionPrefix", () => { it("matches an exact prefix and returns the delta", () => { const got = matchSessionPrefix([cand("s1", [u("hi"), a("hello")])], [u("hi"), a("hello"), u("more")], "m"); expect(got).toEqual({ sessionId: "s1", delta: [u("more")] }); }); it("accepts a multi-message tool delta ending with a tool result", () => { const mirror = [u("hi"), { role: "assistant" as const, content: "", tool_calls: [TOOL_CALL] }]; const incoming = [...mirror, { role: "tool" as const, content: "3pm", tool_call_id: "call_1" }]; const got = matchSessionPrefix([cand("s1", mirror)], incoming, "m"); expect(got?.sessionId).toBe("s1"); expect(got?.delta).toHaveLength(1); expect(got?.delta[0].role).toBe("tool"); }); it("rejects deltas that end with a non-respondable role", () => { const got = matchSessionPrefix([cand("s1", [u("hi")])], [u("hi"), a("stray assistant")], "m"); expect(got).toBeNull(); }); it("never matches when incoming is not longer than the mirror (retry of answered request)", () => { const mirror = [u("hi"), a("hello")]; expect(matchSessionPrefix([cand("s1", mirror)], mirror, "m")).toBeNull(); expect(matchSessionPrefix([cand("s1", mirror)], [u("hi")], "m")).toBeNull(); }); it("rejects diverged histories (prefix mismatch)", () => { const got = matchSessionPrefix([cand("s1", [u("hi"), a("hello")])], [u("hi"), a("DIFFERENT"), u("more")], "m"); expect(got).toBeNull(); }); it("requires the model to match", () => { expect(matchSessionPrefix([cand("s1", [u("hi")], 0, "other")], [u("hi"), u("more")], "m")).toBeNull(); }); it("prefers the longest mirror, then most recently used", () => { const incoming = [u("hi"), a("hello"), u("more")]; const longest = matchSessionPrefix( [cand("short", [u("hi")], 99), cand("long", [u("hi"), a("hello")], 0)], incoming, "m", ); expect(longest?.sessionId).toBe("long"); const tie = matchSessionPrefix( [cand("old", [u("hi"), a("hello")], 1), cand("recent", [u("hi"), a("hello")], 2)], incoming, "m", ); expect(tie?.sessionId).toBe("recent"); }); it("matches an empty mirror (fresh session) against any respondable history", () => { const incoming = [{ role: "system" as const, content: "be nice" }, u("hi")]; const got = matchSessionPrefix([cand("fresh", [])], incoming, "m"); expect(got).toEqual({ sessionId: "fresh", delta: incoming }); }); it("treats content null-normalization as equivalent (tool-call assistant echo)", () => { const mirror = [u("hi"), { role: "assistant" as const, content: "", tool_calls: [TOOL_CALL] }]; // Client echoes the assistant message back with content:"" (normalized from null upstream). const incoming = [u("hi"), { role: "assistant" as const, content: "", tool_calls: [{ ...TOOL_CALL }] }, { role: "tool" as const, content: "x", tool_call_id: "call_1" }]; expect(matchSessionPrefix([cand("s1", mirror)], incoming, "m")?.sessionId).toBe("s1"); }); it("matches leniently when the client reshapes the assistant echo (think-strip + whitespace)", () => { const mirror = [u("hi"), a("plan itHello there\n")]; const got = matchSessionPrefix([cand("s1", mirror)], [u("hi"), a("Hello there"), u("more")], "m"); expect(got).toEqual({ sessionId: "s1", delta: [u("more")] }); }); it("stays strict on user/tool divergence even with lenient assistant matching", () => { const mirror = [u("hi "), a("Hello")]; // User content differs by whitespace — client-authored roles must be byte-exact. expect(matchSessionPrefix([cand("s1", mirror)], [u("hi"), a("Hello"), u("more")], "m")).toBeNull(); }); it("rejects assistant tool_calls divergence under lenient matching", () => { const mirror = [u("hi"), { role: "assistant" as const, content: "x", tool_calls: [TOOL_CALL] }]; const changed = { ...TOOL_CALL, function: { name: "get_time", arguments: '{"tz":"utc"}' } }; const incoming = [u("hi"), { role: "assistant" as const, content: "x ", tool_calls: [changed] }, { role: "tool" as const, content: "t", tool_call_id: "call_1" }]; expect(matchSessionPrefix([cand("s1", mirror)], incoming, "m")).toBeNull(); }); it("prefers a strict match over a lenient one regardless of recency", () => { const incoming = [u("hi"), a("Hello"), u("more")]; const got = matchSessionPrefix( [ cand("lenient", [u("hi"), a("Hello ")], 99), cand("strict", [u("hi"), a("Hello")], 1), ], incoming, "m", ); expect(got?.sessionId).toBe("strict"); }); }); describe("normalizeAssistantContent", () => { it("strips think blocks, collapses whitespace and trims", () => { expect(normalizeAssistantContent("a\nb Hi\n\nthere friend ")).toBe("Hi there friend"); expect(normalizeAssistantContent("xok")).toBe("ok"); expect(normalizeAssistantContent("plain")).toBe("plain"); }); }); describe("trimToRespondable", () => { it("drops trailing assistant/system messages", () => { expect(trimToRespondable([u("hi"), a("partial")])).toEqual([u("hi")]); expect(trimToRespondable([u("hi"), a("x"), { role: "system" as const, content: "nudge" }])).toEqual([u("hi")]); }); it("returns empty when nothing respondable remains", () => { expect(trimToRespondable([a("hello")])).toEqual([]); expect(trimToRespondable([])).toEqual([]); }); it("keeps already-respondable histories unchanged", () => { const msgs = [u("hi"), a("x"), u("more")]; expect(trimToRespondable(msgs)).toEqual(msgs); }); });