




















1+import { describe, expect, it } from "vitest";
2+import type { AgentMessage } from "../../types.js";
3+import type { SessionTreeEntry } from "../types.js";
4+import { estimateTokens, findCutPoint } from "./compaction.js";
5+6+const KEEP_RECENT_TOKENS = 20000;
7+const LARGE_TOOL_OUTPUT = "x".repeat(120000);
8+9+function userText(text: string, timestamp: number): AgentMessage {
10+return { role: "user", content: [{ type: "text", text }], timestamp };
11+}
12+13+function assistantText(text: string, timestamp: number): AgentMessage {
14+return {
15+role: "assistant",
16+content: [{ type: "text", text }],
17+api: "anthropic-messages",
18+provider: "anthropic",
19+model: "claude-fable-5",
20+usage: {
21+input: 0,
22+output: 0,
23+cacheRead: 0,
24+cacheWrite: 0,
25+totalTokens: 0,
26+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
27+},
28+stopReason: "stop",
29+ timestamp,
30+};
31+}
32+33+function toolResultText(text: string, timestamp: number): AgentMessage {
34+return {
35+role: "toolResult",
36+toolCallId: "call-1",
37+toolName: "bash",
38+content: [{ type: "text", text }],
39+isError: false,
40+ timestamp,
41+};
42+}
43+44+function messageEntry(message: AgentMessage, index: number): SessionTreeEntry {
45+return {
46+type: "message",
47+id: `entry-${index}`,
48+parentId: index === 0 ? null : `entry-${index - 1}`,
49+timestamp: new Date(message.timestamp).toISOString(),
50+ message,
51+};
52+}
53+54+function buildTranscript(): SessionTreeEntry[] {
55+const messages: AgentMessage[] = [
56+userText("start of the conversation", 1),
57+assistantText("first reply", 2),
58+userText("please run the command", 3),
59+assistantText("running it now", 4),
60+toolResultText(LARGE_TOOL_OUTPUT, 5),
61+];
62+return messages.map((message, index) => messageEntry(message, index));
63+}
64+65+describe("findCutPoint with a trailing oversized tool result", () => {
66+it("counts the final tool result as larger than the keep budget", () => {
67+const trailing = toolResultText(LARGE_TOOL_OUTPUT, 5);
68+69+expect(estimateTokens(trailing)).toBeGreaterThanOrEqual(KEEP_RECENT_TOKENS);
70+});
71+72+it("trims the prefix instead of keeping the whole transcript", () => {
73+const entries = buildTranscript();
74+75+const result = findCutPoint(entries, 0, entries.length, KEEP_RECENT_TOKENS);
76+77+expect(result.firstKeptEntryIndex).toBeGreaterThan(0);
78+expect(result.firstKeptEntryIndex).toBe(3);
79+});
80+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。