























1+// Covers the chat.history final byte-budget fallback, including the sentinel
2+// that prevents an empty (blank) transcript from being returned to the dashboard.
3+import { describe, expect, it } from "vitest";
4+import { enforceChatHistoryFinalBudget } from "./chat.js";
5+6+type DisplayMessage = {
7+role?: string;
8+content?: Array<{ type?: string; text?: string }>;
9+};
10+11+function firstText(messages: unknown[]): string {
12+const msg = messages[0] as DisplayMessage | undefined;
13+return msg?.content?.[0]?.text ?? "";
14+}
15+16+describe("enforceChatHistoryFinalBudget", () => {
17+it("passes through history that already fits the budget", () => {
18+const messages = [
19+{ role: "user", content: [{ type: "text", text: "hello" }] },
20+{ role: "assistant", content: [{ type: "text", text: "hi" }] },
21+];
22+const result = enforceChatHistoryFinalBudget({ messages, maxBytes: 1_000_000 });
23+expect(result.messages).toEqual(messages);
24+expect(result.placeholderCount).toBe(0);
25+});
26+27+it("returns the empty array unchanged for empty input", () => {
28+const result = enforceChatHistoryFinalBudget({ messages: [], maxBytes: 10 });
29+expect(result.messages).toEqual([]);
30+expect(result.placeholderCount).toBe(0);
31+});
32+33+it("keeps just the last message when the full set is over budget but the last fits", () => {
34+const big = { role: "user", content: [{ type: "text", text: "x".repeat(4000) }] };
35+const last = { role: "assistant", content: [{ type: "text", text: "ok" }] };
36+const result = enforceChatHistoryFinalBudget({ messages: [big, last], maxBytes: 2_000 });
37+expect(result.messages).toEqual([last]);
38+expect(result.placeholderCount).toBe(0);
39+});
40+41+it("falls back to a small placeholder when even the last message is too large", () => {
42+const last = {
43+role: "assistant",
44+timestamp: 1,
45+content: [{ type: "text", text: "y".repeat(4000) }],
46+__openclaw: { id: "abc", seq: 7 },
47+};
48+const result = enforceChatHistoryFinalBudget({ messages: [last], maxBytes: 2_000 });
49+expect(result.messages).toHaveLength(1);
50+expect(firstText(result.messages)).toContain("chat.history omitted: message too large");
51+expect(result.placeholderCount).toBe(1);
52+});
53+54+it("returns a metadata-free sentinel (never an empty transcript) when even the placeholder is over budget", () => {
55+// A pathological message whose oversized-placeholder copy is itself too
56+// large because it carries very large transcript metadata.
57+const hugeId = "z".repeat(4000);
58+const message = {
59+role: "user",
60+timestamp: 1,
61+content: [{ type: "text", text: "hi" }],
62+__openclaw: { id: hugeId, seq: 1 },
63+};
64+const result = enforceChatHistoryFinalBudget({ messages: [message], maxBytes: 1_000 });
65+66+// The critical guarantee: the dashboard never receives an empty history.
67+expect(result.messages).toHaveLength(1);
68+expect(firstText(result.messages)).toContain("chat.history unavailable");
69+// The sentinel does not carry the oversized source metadata.
70+expect((result.messages[0] as Record<string, unknown>)["__openclaw"]).toBeUndefined();
71+expect(result.placeholderCount).toBe(1);
72+});
73+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。