/** * Every request shape we have actually seen, plus the malformed ones a client * could plausibly send. Run with `npm test` — no framework, node's own runner. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { selectMessageSlots, tokenCandidates, textOf } 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: "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: "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, CARD, `${label}: card`); assert.equal(slots.greeting, GREETING, `${label}: greeting`); assert.equal(slots.nameSlot, NAME, `${label}: name`); } }); test("the greeting and the name stay put as a conversation grows", () => { const slots = selectMessageSlots(continued); assert.equal(slots.greeting, GREETING); // Not "keep going" or "and again": the name is what started the chat. assert.equal(slots.nameSlot, NAME); assert.equal(slots.lastUser, "{{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("the token a chat was started with outranks one pasted later, so a chat cannot flip between cards", () => { const candidates = tokenCandidates([ ...withProbe, { role: "assistant", content: `created (v1). ${TOKEN}` }, { role: "user", content: "{{user}}: [sucker:conv=zzz999]" }, ]); const first = candidates.find((c) => c.includes("sucker:conv=")); assert.match(first!, /abc123/); }); test("a token in the card or the greeting is never a candidate", () => { const stale = "[sucker:conv=stale00]"; const candidates = tokenCandidates([ { role: "system", content: `${CARD} made with sucker: ${stale}` }, { role: "user", content: "." }, { role: "assistant", content: `${GREETING} ${stale}` }, { role: "user", content: NAME }, ]); assert.ok(!candidates.some((c) => c.includes(stale)), "an expired token baked into a card would make it unextractable forever"); assert.deepEqual(candidates, [NAME]); }); test("roles are matched whatever their casing", () => { const slots = selectMessageSlots([ { role: "System", content: CARD }, { role: " Assistant ", content: GREETING }, { role: "USER", content: NAME }, ]); assert.equal(slots.system, CARD); assert.equal(slots.greeting, GREETING); assert.equal(slots.nameSlot, NAME); }); test("content sent as OpenAI's multipart array is read as text", () => { assert.equal(textOf([{ type: "text", text: "one " }, { type: "text", text: "two" }]), "one two"); const slots = selectMessageSlots([ { role: "system", content: [{ type: "text", text: CARD }] }, { role: "assistant", content: [{ type: "text", text: GREETING }] }, { role: "user", content: NAME }, ]); assert.equal(slots.system, CARD); assert.equal(slots.greeting, GREETING); }); test("messages with no usable content are skipped, not treated as slots", () => { const slots = selectMessageSlots([ { role: "system", content: CARD }, { role: "assistant", content: null }, { role: "assistant" }, { role: "assistant", content: GREETING }, { role: "user", content: NAME }, ]); // The empty assistant turns must not be mistaken for the greeting. assert.equal(slots.greeting, GREETING); assert.equal(slots.nameSlot, NAME); }); test("a request with roles but no system turn has no card, rather than a misdiagnosed one", () => { const slots = selectMessageSlots([ { role: "assistant", content: GREETING }, { role: "user", content: NAME }, ]); // Taking the greeting as the card would produce a "custom prompt not set" // message for a request the user could never have fixed. assert.equal(slots.system, undefined); assert.equal(slots.greeting, GREETING); }); 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, CARD); }); test("nothing throws on empty or malformed input", () => { for (const input of [[], undefined, [{}, { role: "user" }], [null], [{ content: 42 }]]) { assert.doesNotThrow(() => selectMessageSlots(input as never)); assert.doesNotThrow(() => tokenCandidates(input as never)); } assert.equal(selectMessageSlots([]).greeting, undefined); assert.deepEqual(tokenCandidates(undefined), []); });