This repository was archived by the owner on May 15, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Expand file tree
/
Copy pathnested-condense.spec.ts
More file actions
211 lines (184 loc) · 7.97 KB
/
Copy pathnested-condense.spec.ts
File metadata and controls
211 lines (184 loc) · 7.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import { describe, it, expect } from "vitest"
import { ApiMessage } from "../../task-persistence/apiMessages"
import { getEffectiveApiHistory, getMessagesSinceLastSummary } from "../index"
describe("nested condensing scenarios", () => {
describe("fresh-start model (user-role summaries)", () => {
it("should return only the latest summary and messages after it", () => {
const condenseId1 = "condense-1"
const condenseId2 = "condense-2"
// Simulate history after two nested condenses with user-role summaries
const history: ApiMessage[] = [
// Original task - condensed in first condense
{ role: "user", content: "Build an app", ts: 100, condenseParent: condenseId1 },
// Messages from first condense
{ role: "assistant", content: "Starting...", ts: 200, condenseParent: condenseId1 },
{ role: "user", content: "Add auth", ts: 300, condenseParent: condenseId1 },
// First summary (user role, fresh-start model) - then condensed in second condense
{
role: "user",
content: [{ type: "text", text: "## Summary 1" }],
ts: 399,
isSummary: true,
condenseId: condenseId1,
condenseParent: condenseId2, // Tagged during second condense
},
// Messages after first condense but before second
{ role: "assistant", content: "Auth added", ts: 400, condenseParent: condenseId2 },
{ role: "user", content: "Add database", ts: 500, condenseParent: condenseId2 },
// Second summary (user role, fresh-start model)
{
role: "user",
content: [{ type: "text", text: "## Summary 2" }],
ts: 599,
isSummary: true,
condenseId: condenseId2,
},
// Messages after second condense (kept messages)
{ role: "assistant", content: "Database added", ts: 600 },
{ role: "user", content: "Now test it", ts: 700 },
]
// Step 1: Get effective history
const effectiveHistory = getEffectiveApiHistory(history)
// Should only contain: Summary2, and messages after it
expect(effectiveHistory.length).toBe(3)
expect(effectiveHistory[0].isSummary).toBe(true)
expect(effectiveHistory[0].condenseId).toBe(condenseId2) // Latest summary
expect(effectiveHistory[1].content).toBe("Database added")
expect(effectiveHistory[2].content).toBe("Now test it")
// Verify NO condensed messages are included
const hasCondensedMessages = effectiveHistory.some(
(msg) => msg.condenseParent && history.some((m) => m.isSummary && m.condenseId === msg.condenseParent),
)
expect(hasCondensedMessages).toBe(false)
// Step 2: Get messages since last summary (on effective history)
const messagesSinceLastSummary = getMessagesSinceLastSummary(effectiveHistory)
// Should be the same as effective history since Summary2 is already at the start
expect(messagesSinceLastSummary.length).toBe(3)
expect(messagesSinceLastSummary[0].isSummary).toBe(true)
expect(messagesSinceLastSummary[0].condenseId).toBe(condenseId2)
// CRITICAL: No previous history (Summary1 or original task) should be included
const hasSummary1 = messagesSinceLastSummary.some((m) => m.condenseId === condenseId1)
expect(hasSummary1).toBe(false)
const hasOriginalTask = messagesSinceLastSummary.some((m) => m.content === "Build an app")
expect(hasOriginalTask).toBe(false)
})
it("should handle triple nested condense correctly", () => {
const condenseId1 = "condense-1"
const condenseId2 = "condense-2"
const condenseId3 = "condense-3"
const history: ApiMessage[] = [
// First condense content
{ role: "user", content: "Task", ts: 100, condenseParent: condenseId1 },
{
role: "user",
content: [{ type: "text", text: "## Summary 1" }],
ts: 199,
isSummary: true,
condenseId: condenseId1,
condenseParent: condenseId2,
},
// Second condense content
{ role: "assistant", content: "After S1", ts: 200, condenseParent: condenseId2 },
{
role: "user",
content: [{ type: "text", text: "## Summary 2" }],
ts: 299,
isSummary: true,
condenseId: condenseId2,
condenseParent: condenseId3,
},
// Third condense content
{ role: "assistant", content: "After S2", ts: 300, condenseParent: condenseId3 },
{
role: "user",
content: [{ type: "text", text: "## Summary 3" }],
ts: 399,
isSummary: true,
condenseId: condenseId3,
},
// Current messages
{ role: "assistant", content: "Current work", ts: 400 },
]
const effectiveHistory = getEffectiveApiHistory(history)
// Should only contain Summary3 and current work
expect(effectiveHistory.length).toBe(2)
expect(effectiveHistory[0].condenseId).toBe(condenseId3)
expect(effectiveHistory[1].content).toBe("Current work")
const messagesSinceLastSummary = getMessagesSinceLastSummary(effectiveHistory)
expect(messagesSinceLastSummary.length).toBe(2)
// No previous summaries should be included
const hasPreviousSummaries = messagesSinceLastSummary.some(
(m) => m.condenseId === condenseId1 || m.condenseId === condenseId2,
)
expect(hasPreviousSummaries).toBe(false)
})
})
describe("getMessagesSinceLastSummary behavior with full vs effective history", () => {
it("should return consistent results when called with full history vs effective history", () => {
const condenseId = "condense-1"
const fullHistory: ApiMessage[] = [
{ role: "user", content: "Original task", ts: 100, condenseParent: condenseId },
{ role: "assistant", content: "Response", ts: 200, condenseParent: condenseId },
{
role: "user",
content: [{ type: "text", text: "Summary" }],
ts: 299,
isSummary: true,
condenseId,
},
{ role: "assistant", content: "After summary", ts: 300 },
]
// Called with FULL history (as in summarizeConversation)
const fromFullHistory = getMessagesSinceLastSummary(fullHistory)
// Called with EFFECTIVE history (as in attemptApiRequest)
const effectiveHistory = getEffectiveApiHistory(fullHistory)
const fromEffectiveHistory = getMessagesSinceLastSummary(effectiveHistory)
// Both should return the same messages when summary is user role
expect(fromFullHistory.length).toBe(fromEffectiveHistory.length)
// Both should start with the summary
expect(fromFullHistory[0].isSummary).toBe(true)
expect(fromEffectiveHistory[0].isSummary).toBe(true)
})
it("should not include condensed original task in effective history", () => {
const condenseId1 = "condense-1"
const condenseId2 = "condense-2"
// Scenario: Two nested condenses with user-role summaries
const fullHistory: ApiMessage[] = [
{ role: "user", content: "Original task - should NOT appear", ts: 100, condenseParent: condenseId1 },
{ role: "assistant", content: "Old response", ts: 200, condenseParent: condenseId1 },
// First summary (user role, fresh-start model), then condensed again
{
role: "user",
content: [{ type: "text", text: "Summary 1" }],
ts: 299,
isSummary: true,
condenseId: condenseId1,
condenseParent: condenseId2,
},
{ role: "assistant", content: "After S1", ts: 300, condenseParent: condenseId2 },
// Second summary (user role, fresh-start model)
{
role: "user",
content: [{ type: "text", text: "Summary 2" }],
ts: 399,
isSummary: true,
condenseId: condenseId2,
},
{ role: "assistant", content: "Current message", ts: 400 },
]
const effectiveHistory = getEffectiveApiHistory(fullHistory)
expect(effectiveHistory.length).toBe(2) // Summary2 + Current message
const messagesSinceLastSummary = getMessagesSinceLastSummary(effectiveHistory)
// The original task should NOT be included
const hasOriginalTask = messagesSinceLastSummary.some((m) =>
typeof m.content === "string"
? m.content.includes("Original task")
: JSON.stringify(m.content).includes("Original task"),
)
expect(hasOriginalTask).toBe(false)
// Summary1 should not be included (it was condensed)
const hasSummary1 = messagesSinceLastSummary.some((m) => m.condenseId === condenseId1)
expect(hasSummary1).toBe(false)
})
})
})