Files
time/ventures/havre/tests/abilities.test.mjs
T

289 lines
23 KiB
JavaScript
Raw Normal View History

2026-10-16 07:45:57 +00:00
// node --test tests/*.test.mjs: photos, clips, voice notes, hearing and seeing, without a browser or a network
import { test } from "node:test";
import assert from "node:assert/strict";
import * as C from "../card.js";
import * as A from "../abilities.js";
import { Venice } from "../venice.js";
const ines = C.normalizeCard({
name: "Ines Corbel", tagline: "Ferry pilot with a pencil behind her ear", persona: "You are Ines, who pilots the morning ferry.",
look: { description: "forties, short grey hair, yellow oilskin", references: ["https://example.com/a.png", "javascript:alert(1)", "data:image/svg+xml;base64,PHN2Zz4=", "pics/b.webp", "https://example.com/a.png", "data:image/png;base64,AAAA", "https://example.com/c.jpg"] },
speech: { model: "tts-kokoro", voice: "af_bella", mood: "bright", speed: 9 },
});
test("cards carry how they look and how they sound, within limits", () => {
assert.deepEqual(ines.look.references, ["https://example.com/a.png", "pics/b.webp", "data:image/png;base64,AAAA"], "no scripts, no SVG, no repeats, at most three");
assert.equal(ines.look.description, "forties, short grey hair, yellow oilskin");
assert.deepEqual(ines.speech, { model: "tts-kokoro", voice: "af_bella", mood: "bright", speed: 4 });
const plain = C.normalizeCard({ name: "x", persona: "y" });
assert.deepEqual(plain.look, { description: "", references: [] });
assert.deepEqual(plain.speech, { model: "", voice: "", mood: "", speed: 1 });
assert.equal(C.normalizeCard({ name: "x", speech: { speed: "0.1" } }).speech.speed, 0.25);
assert.deepEqual(C.fromCharacterCard(C.toCharacterCardV3(ines)), ines, "a card with pictures and a voice comes back whole");
});
// ---------- picking models ----------
const photos = [
{ id: "qwen-image-3-edit", name: "Qwen Image 3", privacy: "anonymized", price: 0.04, maxIn: 3, included: 0, extra: null },
{ id: "firered-image-edit", name: "FireRed", privacy: "private", price: 0.04, maxIn: 3, included: 1, extra: null },
{ id: "flux-2-max-edit", name: "Flux 2 Max", privacy: "anonymized", price: 0.12, maxIn: 6, included: 1, extra: 0.0345 },
];
test("the model in use: the one picked if Venice still has it, else Havre's preference, else a private one", () => {
assert.equal(A.chosen(photos, "flux-2-max-edit", "photo").id, "flux-2-max-edit");
assert.equal(A.chosen(photos, "gone-model", "photo").id, "firered-image-edit");
assert.equal(A.chosen(photos.filter((m) => m.id !== "firered-image-edit"), "", "photo").id, "qwen-image-3-edit");
assert.equal(A.chosen(null, "x", "photo"), null);
const video = [{ id: "wan-2-7-enhanced-image-to-video", ref: false }, { id: "wan-2-7-reference-to-video", ref: true, privacy: "anonymized" },
{ id: "x-enhanced-reference-to-video", ref: true }, { id: "y-reference-to-video", ref: true, uncensored: true },
{ id: "seedance-2-5-us-reference-to-video-private", ref: true, privacy: "private" }];
const clips = video.filter(A.isClipModel);
assert.deepEqual(clips.map((m) => m.id), ["wan-2-7-reference-to-video", "seedance-2-5-us-reference-to-video-private"], "reference-to-video only, and no adult variants");
assert.equal(A.chosen(clips, "", "clip").id, "seedance-2-5-us-reference-to-video-private");
const text = [{ id: "a", vision: true, privacy: "anonymized", input: 0.1 }, { id: "b", vision: true, privacy: "private", input: 0.9 },
{ id: "c", vision: true, privacy: "private", input: 0.3 }, { id: "d", vision: false, privacy: "private", input: 0.01 }];
assert.equal(A.seer(text).id, "c", "the cheapest private model that can see");
assert.equal(A.seer(text, "a").id, "a");
assert.equal(A.seer([{ id: "d", vision: false }]), null);
});
test("prices: a photo with extra pictures, a voice note, a minute of hearing, written plainly", () => {
assert.equal(A.photoPrice(photos[2], 1), 0.12);
assert.equal(A.photoPrice(photos[2], 3), 0.189);
assert.equal(A.photoPrice(photos[0], 3), 0.04);
assert.equal(A.photoPrice({ price: null }, 1), null);
assert.equal(A.voicePrice({ perMChars: 3.5 }, 200), 0.0007);
assert.equal(A.hearingPerMinute({ perSecond: 0.0001 }).toFixed(4), "0.0060");
assert.deepEqual([A.usd(0.03), A.usd(1.4), A.usd(0.0007), A.usd(0.00072), A.usd(187.5), A.usd(0), A.usd(null), A.usd(0.00001)],
["$0.03", "$1.40", "$0.0007", "$0.00072", "$188", "$0", "?", "under $0.0001"]);
});
// ---------- the shot ----------
test("the shot is written from the conversation, with the rules, and the photo keeps the character", () => {
const hist = [{ role: "user", content: "[Start the conversation…]", hidden: true }, { role: "assistant", content: "Ticket? Kidding." },
{ role: "user", content: "Show me the view from the wheelhouse" }];
const [sys, user] = A.shotMessages(ines, hist, "at sunset", "photo");
assert.equal(sys.content, A.PHOTO_SYSTEM);
assert.match(sys.content, /safe for work/);
assert.match(sys.content, /Never show a real, identifiable person/);
assert.match(user.content, /Ines Corbel: Ticket\? Kidding\./);
assert.match(user.content, /Them: Show me the view from the wheelhouse/);
assert.ok(!user.content.includes("Start the conversation"), "what started it stays out");
assert.match(user.content, /How they look: forties/);
assert.match(user.content, /What was asked for: at sunset/);
assert.equal(A.shotMessages(ines, [], "", "clip")[0].content, A.CLIP_SYSTEM);
assert.match(A.shotMessages(ines, [], "")[1].content, /nothing in particular/);
assert.equal(A.cleanText('<think>hmm</think>Shot: "The character from the reference picture leans on the rail."'), "The character from the reference picture leans on the rail.");
const p = A.photoPrompt(ines, "She leans on the rail at dusk.", 2, 120);
assert.ok(p.startsWith("The reference pictures show Ines Corbel."));
assert.ok(p.length <= 120);
assert.match(A.portraitPrompt(ines), /^Portrait photo of Ines Corbel, an original adult character: forties.*Safe for work: fully clothed, nothing sexual, no gore; everyone shown is an adult\./);
assert.match(A.photoPrompt(ines, "On deck."), /Keep how they look exactly.*Safe for work.*everyone shown is an adult\. New photo: On deck\./);
assert.match(A.clipPrompt(ines, "She waves."), /everyone shown is an adult\. She waves\.$/);
assert.match(A.lookMessages(ines)[1].content, /Ferry pilot/);
2026-10-16 10:43:10 +00:00
// a picture for their card, made from the ones they have: the next view of a character sheet
assert.deepEqual([0, 1, 2, 3, -1, NaN].map(A.sheetView), [A.SHEET_VIEWS[0], A.SHEET_VIEWS[1], A.SHEET_VIEWS[2], A.SHEET_VIEWS[0], A.SHEET_VIEWS[2], A.SHEET_VIEWS[0]]);
assert.match(A.photoPrompt(ines, A.sheetView(0)), /^The reference picture shows Ines Corbel\. Keep how they look exactly.*Safe for work.*New photo: Full length, standing/);
2026-10-16 07:45:57 +00:00
});
test("voice notes read the words, not the code or the stage directions", () => {
assert.equal(A.speakable("<think>x</think>Sure! *waves*\n\n```js\nlet a\n```\nBye."), "Sure! Bye.");
assert.equal(A.speakable("a".repeat(5000)).length, 4000);
});
test("clip options: what was picked last time where the model allows it, else 5 seconds, 720p, tall", () => {
const m = { durations: ["1s", "5s", "10s"], resolutions: ["480p", "720p"], aspects: ["16:9", "9:16"], audioConfigurable: true };
assert.deepEqual(A.clipOptions(m), { duration: "5s", resolution: "720p", aspect: "9:16", audio: true });
assert.deepEqual(A.clipOptions(m, { duration: "10s", resolution: "1080p", aspect: "16:9", audio: false }), { duration: "10s", resolution: "720p", aspect: "16:9", audio: false });
assert.deepEqual(A.clipOptions({ durations: ["2s", "6s"], resolutions: [], aspects: [] }), { duration: "6s", resolution: "", aspect: "" });
});
test("the conversation as the model reads it: a photo it sent is mentioned to it, yours is words", () => {
const hist = [
{ role: "user", content: "hi", demo: true },
{ role: "user", content: "send me one" },
{ role: "assistant", content: "", media: { kind: "photo", id: "p1", shot: "On the deck." } },
{ role: "assistant", content: "", media: { kind: "clip", id: "c1", shot: "Waving." } },
{ role: "user", content: "lovely", media: null },
{ role: "user", content: "what's this?", media: { kind: "photo-in", id: "u1", saw: "A gull on a post." } },
{ role: "assistant", content: "A gull!", voice: { id: "v1" } },
{ role: "user", content: "hello there", media: { kind: "voice-in", id: "u2" } },
];
const api = A.apiHistory(hist);
assert.deepEqual(api.map((m) => m.role), ["user", "user", "user", "assistant", "user"]);
assert.equal(api[1].content, "(You sent me a photo: On the deck.) (You sent me a short video: Waving.)\n\nlovely");
assert.equal(api[2].content, "[a photo: A gull on a post.] what's this?");
assert.equal(api[4].content, "hello there");
assert.ok(!api.some((m) => /\[photo\]/.test(m.content)), "the model never sees a photo placeholder in its own voice");
const seen = A.withPicture([{ role: "system", content: "s" }, { role: "user", content: "[a photo] look" }], "data:image/jpeg;base64,AAAA");
assert.deepEqual(seen[1].content[1], { type: "image_url", image_url: { url: "data:image/jpeg;base64,AAAA" } });
assert.equal(seen[1].content[0].text, "[a photo] look");
assert.deepEqual([...A.mediaIds({ a: hist, b: "junk" })].sort(), ["c1", "p1", "u1", "u2", "v1"]);
});
// ---------- venice.js: the media calls, against a fake fetch ----------
function fake(handler) {
const calls = [];
const f = async (url, init = {}) => {
calls.push({ url, init, body: typeof init.body === "string" ? JSON.parse(init.body) : init.body });
const r = handler(url.replace("https://api.venice.ai/api/v1", ""), init);
const headers = new Headers(r.headers || { "content-type": r.blob ? "application/octet-stream" : "application/json" });
return { ok: r.status < 400, status: r.status, headers, json: async () => r.body, blob: async () => new Blob([r.blob || ""], { type: headers.get("content-type") }) };
};
return { f, calls };
}
test("venice: photo edits keep safe mode on, one picture or several; a first portrait asks for the bytes", async () => {
const { f, calls } = fake(() => ({ status: 200, blob: "img", headers: { "content-type": "image/webp", "x-venice-is-blurred": "true" } }));
const v = new Venice("sk", { fetch: f });
const one = await v.editImage({ model: "grok-imagine-edit", prompt: "p", images: ["data:image/png;base64,QUJD"] });
assert.equal(calls[0].url, "https://api.venice.ai/api/v1/image/edit");
assert.deepEqual(calls[0].body, { model: "grok-imagine-edit", prompt: "p", image: "QUJD", safe_mode: true, output_format: "webp" });
assert.equal(one.blurred, true);
assert.equal(await one.blob.text(), "img");
await v.editImage({ model: "m", prompt: "p", images: ["https://venice.ai/_next/image?url=x&w=1080&q=75", "data:image/webp;base64,REVG"] });
assert.equal(calls[1].url, "https://api.venice.ai/api/v1/image/multi-edit");
assert.deepEqual(calls[1].body, { modelId: "m", prompt: "p", images: ["https://venice.ai/_next/image?url=x&w=1080&q=75", "REVG"], safe_mode: true, output_format: "webp" });
await v.generateImage({ model: "grok-imagine-image", prompt: "a portrait", aspect: "1:1" });
assert.deepEqual(calls[2].body, { model: "grok-imagine-image", prompt: "a portrait", safe_mode: true, format: "webp", return_binary: true, aspect_ratio: "1:1" });
await assert.rejects(v.editImage({ model: "m", prompt: "p", images: [] }), /at least one picture/);
});
test("venice: a voice note sends the mood as the style prompt; hearing sends the recording as a form", async () => {
const { f, calls } = fake((p) => (p === "/audio/transcriptions" ? { status: 200, body: { text: " hello from the mic ", duration: 2.5 } } : { status: 200, blob: "mp3", headers: { "content-type": "audio/mpeg" } }));
const v = new Venice("sk", { fetch: f });
const b = await v.speak({ model: "tts-kokoro", voice: "af_sky", input: "Hi.", mood: "warm", speed: 1.15 });
assert.equal(b.type, "audio/mpeg");
assert.deepEqual(calls[0].body, { input: "Hi.", model: "tts-kokoro", speed: 1.15, voice: "af_sky", prompt: "warm" });
const heard = await v.transcribe({ file: new Blob(["webm"], { type: "audio/webm" }), name: "v.webm", model: "nvidia/parakeet-tdt-0.6b-v3" });
assert.deepEqual(heard, { text: "hello from the mic", duration: 2.5 });
const form = calls[1].init.body;
assert.ok(form instanceof FormData);
assert.equal(form.get("model"), "nvidia/parakeet-tdt-0.6b-v3");
assert.equal(form.get("response_format"), "json");
assert.equal(form.get("file").name, "v.webm");
assert.ok(!("Content-Type" in calls[1].init.headers), "the browser sets the form's own content type");
});
test("venice: a clip is quoted, queued with the character's pictures, polled, and brought home", async () => {
let polls = 0;
const { f, calls } = fake((p, init) => {
if (p === "/video/quote") return { status: 200, body: { quote: 0.47 } };
if (p === "/video/queue") return { status: 200, body: { model: "m", queue_id: "q1", download_url: "https://private-share.venice.ai/v1/share/read/abc" } };
if (p === "/video/retrieve") return ++polls < 2 ? { status: 200, body: { status: "PROCESSING", average_execution_time: 90000, execution_duration: 30000 } }
: { status: 200, blob: "mp4", headers: { "content-type": "video/mp4" } };
if (p === "/video/complete") return { status: 200, body: { success: true } };
if (p.startsWith("https://private-share.venice.ai/")) return init.method === "DELETE" ? { status: 204, body: null } : { status: 200, blob: "mp4", headers: { "content-type": "video/mp4" } };
return { status: 404, body: {} };
});
const v = new Venice("sk", { fetch: f });
assert.equal(await v.quoteVideo({ model: "grok-imagine-1-5-reference-to-video-private", duration: "5s", resolution: "720p", aspect: "9:16" }), 0.47);
assert.deepEqual(calls[0].body, { model: "grok-imagine-1-5-reference-to-video-private", duration: "5s", resolution: "720p", aspect_ratio: "9:16" });
const q = await v.queueVideo({ model: "grok-imagine-1-5-reference-to-video-private", prompt: "waves", duration: "5s", images: ["https://x/a.png", "data:image/png;base64,AA"], audio: true });
assert.deepEqual(q, { queueId: "q1", downloadUrl: "https://private-share.venice.ai/v1/share/read/abc" });
assert.deepEqual(calls[1].body.reference_image_urls, ["https://x/a.png", "data:image/png;base64,AA"]);
assert.equal(calls[1].body.audio, true);
await v.queueVideo({ model: "wan-2-7-image-to-video", prompt: "p", duration: "5s", images: ["https://x/a.png", "https://x/b.png"] });
assert.equal(calls[2].body.image_url, "https://x/a.png", "an image-to-video model gets the first picture");
assert.ok(!("reference_image_urls" in calls[2].body));
assert.deepEqual(await v.retrieveVideo({ model: "m", queueId: "q1" }), { done: false, elapsed: 30000, expected: 90000 });
const done = await v.retrieveVideo({ model: "m", queueId: "q1" });
assert.equal(done.done, true);
assert.equal(await done.blob.text(), "mp4");
assert.deepEqual(calls[4].body, { model: "m", queue_id: "q1" }, "Venice keeps its copy until the clip is home");
const clip = await v.downloadVideo("https://private-share.venice.ai/v1/share/read/abc");
assert.equal(await clip.text(), "mp4");
assert.ok(!calls[5].init.headers, "no key goes to the download link");
assert.equal(calls.length, 6, "the link stays until the clip is stored");
assert.equal(await v.revokeDownload("https://private-share.venice.ai/v1/share/read/abc"), true);
assert.equal(calls[6].init.method, "DELETE", "then it's revoked");
assert.ok(!calls[6].init.headers, "without the key");
assert.equal(await v.revokeDownload("https://evil.example/x"), false);
await assert.rejects(v.downloadVideo("https://evil.example/x"), /isn't a Venice download link/);
const failed = new Venice("sk", { fetch: fake(() => ({ status: 200, body: { status: "FAILED" } })).f });
await assert.rejects(failed.retrieveVideo({ model: "m", queueId: "q" }), (e) => e.status === -2 && /couldn't make the clip \(failed\)/.test(e.message));
assert.equal(await v.completeVideo({ model: "m", queueId: "q1" }), true);
});
test("venice: the other model lists come with their prices, voices, lengths and limits", async () => {
const specs = {
inpaint: [{ id: "grok-imagine-edit", model_spec: { name: "Grok Imagine", privacy: "private", pricing: { inpaint: { usd: 0.03 }, resolutions: { "1K": { usd: 0.03 } }, inputImages: { included: 0, additional: { usd: 0.002 } } }, constraints: { combineImages: true, promptCharacterLimit: 4000 } } },
{ id: "luma-uni-1-edit", model_spec: { name: "Luma", pricing: { inpaint: { usd: 0.06 } }, constraints: { combineImages: false } } },
{ id: "off", model_spec: { offline: true } }],
tts: [{ id: "tts-kokoro", model_spec: { name: "Kokoro", privacy: "private", pricing: { input: { usd: 3.5 } }, voices: ["af_sky", "am_adam"], default_format: "mp3" } }],
asr: [{ id: "nvidia/parakeet-tdt-0.6b-v3", model_spec: { name: "Parakeet", privacy: "private", pricing: { per_audio_second: { usd: 0.0001 } } } }],
video: [{ id: "grok-imagine-1-5-reference-to-video-private", model_spec: { name: "Grok Imagine 1.5 R2V", privacy: "private", constraints: { model_type: "image-to-video", durations: ["5s"], resolutions: ["480p", "720p"], aspect_ratios: ["9:16"], audio: true, audio_configurable: false, prompt_character_limit: 4096 } } }],
image: [{ id: "grok-imagine-image", model_spec: { name: "Grok Imagine", pricing: { resolutions: { "1K": { usd: 0.03 } } }, constraints: { aspectRatios: ["1:1"] } } }],
};
const v = new Venice("", { fetch: fake((p) => ({ status: 200, body: { data: specs[new URL("https://x" + p).searchParams.get("type")] } })).f });
const [g, l] = await v.mediaModels("inpaint");
assert.equal((await v.mediaModels("inpaint")).length, 2, "offline models are left out");
assert.deepEqual([g.price, g.extra, g.included, g.maxIn, g.promptLimit], [0.03, 0.002, 0, 3, 4000]);
assert.equal(l.maxIn, 1, "a model that can't combine pictures takes one");
const [k] = await v.mediaModels("tts");
assert.deepEqual([k.perMChars, k.voices, k.format], [3.5, ["af_sky", "am_adam"], "mp3"]);
assert.equal((await v.mediaModels("asr"))[0].perSecond, 0.0001);
const [r] = await v.mediaModels("video");
assert.deepEqual([r.ref, r.durations, r.resolutions, r.aspects, r.audio, r.audioConfigurable, r.promptLimit], [true, ["5s"], ["480p", "720p"], ["9:16"], true, false, 4096]);
assert.deepEqual((await v.mediaModels("image"))[0].aspects, ["1:1"]);
});
test("venice: what Venice says when it turns something down", async () => {
const v = new Venice("sk", { fetch: fake((p) => (p === "/video/queue" ? { status: 422, body: { error: "Prompt violates content policy" } } : { status: 503, body: {} })).f });
await assert.rejects(v.queueVideo({ model: "m", prompt: "p", duration: "5s" }), /content rules: Prompt violates content policy/);
await assert.rejects(v.speak({ model: "m", input: "x" }), /busy at Venice/);
});
// ---------- on their own ----------
test("on their own: a move is considered when asked to show or say something, when the reply shows something, or now and then", () => {
const u = (content, extra = {}) => ({ role: "user", content, ...extra }), a = (content, extra = {}) => ({ role: "assistant", content, ...extra });
assert.equal(A.moveLikely([u("hi"), a("Hey.")]), false);
assert.equal(A.moveLikely([u("send me a pic?"), a("Hm.")]), true);
assert.equal(A.moveLikely([u("whisper it"), a("Okay.")]), true);
assert.equal(A.moveLikely([u("so?"), a("Just got back from the market, look at this haul")]), true);
assert.equal(A.moveLikely([u("send me a pic?")]), false, "only right after their reply");
assert.equal(A.moveLikely([u("a"), a("1"), u("b"), a("2"), u("c"), a("3"), u("d"), a("4"), u("e"), a("5")]), false);
assert.equal(A.moveLikely([u("a"), a("1"), u("b"), a("2"), u("c"), a("3"), u("d"), a("4"), u("e"), a("5"), u("f"), a("6")]), true, "every sixth reply");
for (const said of ["haha", "here's the thing", "that made me smile", "okay"]) assert.equal(A.moveLikely([u("hm"), a(said)]), false, said);
assert.equal(A.moveLikely([u("[hidden]", { hidden: true }), a("Hi there, look at this view")]), true, "the opener doesn't count as something said");
const own = [u("pic?"), a("ok"), a("", { media: { kind: "photo", own: true } }), u("nice"), a("thanks")];
assert.equal(A.sentLately(own), true);
assert.equal(A.sentLately([...own, u("1"), a("2"), u("3"), a("4")]), false);
assert.deepEqual(A.normalizeMove({ move: "photo", shot: " On the pier. " }), { move: "photo", shot: "On the pier." });
assert.deepEqual(A.normalizeMove({ move: "photo", shot: "" }), { move: "nothing", shot: "" }, "no shot, no photo");
assert.deepEqual(A.normalizeMove({ move: "voice", shot: "x" }), { move: "voice", shot: "" });
assert.deepEqual(A.normalizeMove({ move: "dance" }), { move: "nothing", shot: "" });
assert.deepEqual(A.normalizeMove(null), { move: "nothing", shot: "" });
const m = A.moveMessages(ines, [u("Send me a pic"), a("Okay!")]);
assert.equal(m[0].content, A.MOVE_SYSTEM);
assert.match(m[0].content, /Never two photos in a row/);
assert.match(m[0].content, /Never show a real, identifiable person/);
assert.match(m[1].content, /Them: Send me a pic\nInes Corbel: Okay!$/);
});
test("on their own: a daily allowance, and a hello first after a while", () => {
let rec = null;
rec = A.spend(rec, "2026-10-16", 0.03);
rec = A.spend(rec, "2026-10-16", 0.0007);
assert.equal(A.spentToday(rec, "2026-10-16"), 0.0307);
assert.equal(A.spentToday(rec, "2026-10-17"), 0, "a new day starts at nothing");
assert.equal(A.canSpend(rec, "2026-10-16", 0.25, 0.2), true);
assert.equal(A.canSpend(rec, "2026-10-16", 0.25, 0.23), false);
assert.equal(A.canSpend(rec, "2026-10-17", 0.25, 0.25), true);
assert.equal(A.canSpend(rec, "2026-10-17", 0.25, null), false, "an unknown price never fits");
assert.equal(A.canSpend(rec, "2026-10-17", 0.25, NaN), false);
assert.deepEqual(A.spend({ day: "2026-10-17", usd: 0.01 }, "2026-10-17", -0.05), { day: "2026-10-17", usd: 0 }, "a refund never goes below zero");
// a call's cost, from Venice's usage and the model's prices; unknown prices count high
assert.equal(A.callCost({ prompt_tokens: 1000, completion_tokens: 100 }, { input: 1, output: 4 }), 0.0014);
assert.equal(A.callCost({ prompt_tokens: 1000, completion_tokens: 100 }, null), 0.0045);
assert.equal(A.callCost({ total_tokens: 2000 }, { input: 0.5, output: 2 }), 0.001);
const est = A.callEstimate([{ role: "user", content: "x".repeat(3000) }], 300, { input: 1, output: 4 });
assert.ok(est >= A.callCost({ prompt_tokens: 1000, completion_tokens: 300 }, { input: 1, output: 4 }), "the estimate covers the call");
assert.deepEqual(A.spend(rec, "2026-10-17", 0.1), { day: "2026-10-17", usd: 0.1 });
const now = Date.parse("2026-10-16T20:00:00Z");
assert.equal(A.since([{ role: "user", content: "x", at: now - 9 * 3600e3 }, { role: "assistant", content: "y", at: now - 8 * 3600e3 }], now), 8 * 3600e3);
assert.equal(A.since([{ role: "user", content: "old, no time" }], now), 0, "no time, no hello");
assert.match(A.comeBack(8 * 3600e3), /^\[Time passes: about 8 hours since you last talked\. .*Text them first/);
2026-10-16 09:25:35 +00:00
assert.match(A.comeBack(8 * 3600e3, false), /^\[Time passes: about 8 hours since you last talked\. Reach out to them first/, "from a chat you aren't in");
2026-10-16 07:45:57 +00:00
assert.match(A.comeBack(30 * 3600e3), /about a day/);
assert.match(A.comeBack(80 * 3600e3), /about 3 days/);
});