import assert from "node:assert/strict"; import test from "node:test"; import { CONSULTATION_HISTORY_MESSAGE_CHARS, clipConsultationHistoryText, consultationHistoryFromStoredMessages, consultationHistoryWindow, consultationUserTurnContent, historyBudgetChars, isContextOverflowError, omissionMarker, SESSION_CONTEXT_SUMMARY_HEADING, } from "../src/lib/consultation-session-history.ts"; function numberedMessages(count: number, text: (index: number) => string) { return Array.from({ length: count }, (_, index) => ({ role: index % 2 === 0 ? "user" : "assistant", text: text(index), requestId: `req-${index}`, })); } test("20 unspecialized messages at 128k drop whole oldest turns to fit the budget", () => { const body = "x".repeat(3_000); const history = consultationHistoryWindow(numberedMessages(20, () => body), null, { contextWindow: 128_000, }); const budget = historyBudgetChars(128_000); assert.equal(budget, 40_000); assert.ok(history.droppedCount > 0); assert.equal(history.tail.length, 20 - history.droppedCount); assert.equal(history.tail[0]?.text, body); assert.ok(history.tail.reduce((sum, message) => sum + message.text.length, 0) <= budget); assert.equal(history.summaryText, null); }); test("a summary throughMessageIndex keeps only later turns in the tail", () => { const messages = numberedMessages(20, (index) => `keep-${index}`); const history = consultationHistoryWindow(messages, { version: 1, text: "先前结论", throughRequestId: "req-14", throughMessageIndex: 14, messageCount: 15, updatedAt: "2026-09-06T00:00:00.000Z", }, { contextWindow: 128_000 }); assert.deepEqual(history.tail.map((message) => message.text), [ "keep-15", "keep-16", "keep-17", "keep-18", "keep-19", ]); assert.equal(history.summaryText, "先前结论"); assert.equal(history.droppedCount, 0); }); test("a 20_000 character message is clipped to 12_000 with an omission count", () => { const text = "y".repeat(20_000); const clipped = clipConsultationHistoryText(text); assert.equal(clipped.startsWith("y".repeat(CONSULTATION_HISTORY_MESSAGE_CHARS)), true); assert.equal(clipped.includes(omissionMarker(8_000)), true); assert.equal(clipped.length, CONSULTATION_HISTORY_MESSAGE_CHARS + omissionMarker(8_000).length); const history = consultationHistoryWindow([ { role: "assistant", text }, ], null, { contextWindow: 128_000 }); assert.equal(history.tail[0]?.text, clipped); }); test("historyBudgetChars uses 64k, 32k, and null-as-128k windows", () => { assert.equal(historyBudgetChars(64_000), 6_000); assert.equal(historyBudgetChars(32_000), 4_000); assert.equal(historyBudgetChars(null), historyBudgetChars(128_000)); assert.equal(historyBudgetChars(undefined), 40_000); }); test("stored consultation history can skip the in-flight request id", () => { const history = consultationHistoryFromStoredMessages([ { role: "user", text: "old", requestId: "keep" }, { role: "user", text: "current", requestId: "skip" }, ], { excludeRequestId: "skip" }); assert.deepEqual(history, [{ role: "user", text: "old" }]); }); test("the summary block sits after the time line and before the question", () => { const content = consultationUserTurnContent({ currentTime: "当前时间:2026-09-06 14:00(中国)", name: "测", instruction: "先加载 Jyotish Skill", summaryText: "上次应期在 2027 年", question: "你前面说的应期是哪年", }); const timeAt = content.indexOf("当前时间:"); const summaryAt = content.indexOf(SESSION_CONTEXT_SUMMARY_HEADING); const questionAt = content.indexOf("你前面说的应期是哪年"); assert.ok(timeAt >= 0 && summaryAt > timeAt && questionAt > summaryAt); }); test("context overflow detection covers provider codes and related 400s", () => { assert.equal(isContextOverflowError({ code: "context_length_exceeded" }), true); assert.equal(isContextOverflowError(new Error("maximum context length exceeded")), true); assert.equal(isContextOverflowError({ message: "prompt is too long" }), true); assert.equal(isContextOverflowError({ message: "input is too long" }), true); assert.equal(isContextOverflowError({ message: "too many tokens in the prompt" }), true); assert.equal(isContextOverflowError({ status: 400, message: "max_tokens for this context" }), true); assert.equal(isContextOverflowError({ status: 400, message: "invalid json" }), false); assert.equal(isContextOverflowError(new Error("rate limit")), false); });