/** * Both request shapes JanitorAI sends, and conversations that keep going. * Run with `npm test` — no test framework, just node's built-in runner. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { selectMessageSlots, tokenCandidates } from "../src/lib/messages.ts"; const CARD = "<.>\na tired innkeeper."; const GREETING = "The clink of bottles is the only greeting you get."; const NAME = "{{user}}: Gaius"; const TOKEN = "[sucker:conv=abc123]"; /** What most clients send: a throwaway "." user turn before the greeting. */ const withProbe: { role: string; content: string }[] = [ { role: "system", content: CARD }, { role: "user", content: "." }, { role: "assistant", content: GREETING }, { role: "user", content: NAME }, ]; /** What some clients send: no "." turn, so every index shifts by one. */ const withoutProbe: { role: string; content: string }[] = [ { role: "system", content: CARD }, { role: "assistant", content: GREETING }, { role: "user", content: NAME }, ]; /** Three turns in. Only sucker's FIRST reply carries the token. */ const continued = [ ...withProbe, { role: "assistant", content: `New character "Gaius" created (v1). ${TOKEN}` }, { role: "user", content: "{{user}}: keep going" }, { role: "assistant", content: "Character data unchanged (v1, message 2)." }, { role: "user", content: "{{user}}: and again" }, ]; test("both shapes resolve to the same slots", () => { for (const [label, messages] of [ ["with probe", withProbe], ["without probe", withoutProbe], ] as const) { const slots = selectMessageSlots(messages); assert.equal(slots.system?.content, CARD, `${label}: card`); assert.equal(slots.greeting?.content, GREETING, `${label}: greeting`); assert.equal(slots.nameSlot?.content, NAME, `${label}: name`); } }); test("the greeting and the name stay put as a conversation grows", () => { const slots = selectMessageSlots(continued); assert.equal(slots.greeting?.content, GREETING); // Not "keep going" or "and again": the name is what started the chat. assert.equal(slots.nameSlot?.content, NAME); assert.equal(slots.lastUser?.content, "{{user}}: and again"); }); test("the token is still found once later replies stop carrying it", () => { const found = tokenCandidates(continued).find((c) => c.includes("sucker:conv=")); assert.ok(found, "a token must still be discoverable three turns in"); assert.match(found!, /\[sucker:conv=abc123\]/); }); test("a token the user just pasted outranks an older one", () => { const candidates = tokenCandidates([ ...withProbe, { role: "assistant", content: `created (v1). ${TOKEN}` }, { role: "user", content: "{{user}}: [sucker:conv=zzz999]" }, ]); assert.match(candidates[0], /zzz999/); }); test("a card with no role on the system turn still resolves", () => { const slots = selectMessageSlots([ { content: CARD }, { role: "assistant", content: GREETING }, { role: "user", content: NAME }, ]); assert.equal(slots.system?.content, CARD); }); test("nothing throws on empty or malformed input", () => { for (const input of [[], undefined, [{}, { role: "user" }]]) { assert.doesNotThrow(() => selectMessageSlots(input as never)); assert.doesNotThrow(() => tokenCandidates(input as never)); } assert.equal(selectMessageSlots([]).greeting, undefined); assert.deepEqual(tokenCandidates(undefined), []); });