@polpo-ai/core 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context-compaction-e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compaction-e2e.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/context-compaction-e2e.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Context Compaction E2E test — verifies compaction triggers and works
3
+ * with realistic message sequences that exceed the context window.
4
+ *
5
+ * Uses a tiny contextWindow (2000 tokens) so we can trigger compaction
6
+ * with just a few messages instead of needing 200K tokens.
7
+ */
8
+ import { describe, it, expect, vi } from "vitest";
9
+ import { compactIfNeeded, estimateMessagesTokens, } from "../context-compactor.js";
10
+ // ── Helpers ──────────────────────────────────────────────
11
+ /** Create a user message */
12
+ function userMsg(text) {
13
+ return { role: "user", content: text, timestamp: Date.now() };
14
+ }
15
+ /** Create an assistant text message */
16
+ function assistantTextMsg(text) {
17
+ return {
18
+ role: "assistant",
19
+ content: [{ type: "text", text }],
20
+ timestamp: Date.now(),
21
+ };
22
+ }
23
+ /** Create an assistant tool call message */
24
+ function assistantToolCallMsg(toolName, args) {
25
+ return {
26
+ role: "assistant",
27
+ content: [{ type: "toolCall", id: `call_${Math.random().toString(36).slice(2)}`, name: toolName, arguments: args }],
28
+ timestamp: Date.now(),
29
+ };
30
+ }
31
+ /** Create a tool result message with large output */
32
+ function toolResultMsg(toolCallId, toolName, output) {
33
+ return {
34
+ role: "toolResult",
35
+ toolCallId,
36
+ toolName,
37
+ content: [{ type: "text", text: output }],
38
+ timestamp: Date.now(),
39
+ };
40
+ }
41
+ /** Generate a large string of N tokens (approx) */
42
+ function generateLargeText(targetTokens) {
43
+ // 4 chars per token
44
+ return "x".repeat(targetTokens * 4);
45
+ }
46
+ /** Build a conversation with many tool calls that fills up context */
47
+ function buildLongConversation(numToolCalls, outputTokensEach) {
48
+ const messages = [
49
+ userMsg("Fix the authentication bug in the login page"),
50
+ ];
51
+ for (let i = 0; i < numToolCalls; i++) {
52
+ const tc = assistantToolCallMsg("read_file", { path: `/src/file_${i}.ts` });
53
+ const toolCallId = tc.content[0].id;
54
+ messages.push(tc);
55
+ messages.push(toolResultMsg(toolCallId, "read_file", generateLargeText(outputTokensEach)));
56
+ }
57
+ // Final assistant response
58
+ messages.push(assistantTextMsg("I've reviewed all the files. Here's my analysis..."));
59
+ return messages;
60
+ }
61
+ // ── Tests ────────────────────────────────────────────────
62
+ describe("Context Compaction E2E", () => {
63
+ // Tiny context window so we can trigger compaction easily
64
+ const smallConfig = {
65
+ contextWindow: 2000, // 2K tokens
66
+ maxOutputTokens: 200, // 200 output tokens
67
+ // usable = 1800, trigger at 85% = 1530
68
+ };
69
+ const summarizeFn = vi.fn(async (_messages, _prompt) => {
70
+ return "Summary: The agent was fixing an auth bug. Read 10 files. Found the issue in auth.ts line 42. Modified login handler.";
71
+ });
72
+ it("does NOT compact when context is small", async () => {
73
+ const messages = [
74
+ userMsg("Hello"),
75
+ assistantTextMsg("Hi! How can I help?"),
76
+ ];
77
+ const result = await compactIfNeeded({
78
+ systemPrompt: "You are an agent.",
79
+ messages,
80
+ config: smallConfig,
81
+ summarize: summarizeFn,
82
+ mode: "task",
83
+ });
84
+ expect(result.compacted).toBe(false);
85
+ expect(result.pruned).toBe(false);
86
+ expect(result.messages).toEqual(messages);
87
+ expect(summarizeFn).not.toHaveBeenCalled();
88
+ });
89
+ it("prunes tool outputs when context exceeds threshold", async () => {
90
+ // Use a larger context window where pruning alone is enough
91
+ // 8000 token window, 800 output → usable 7200, trigger at 6120
92
+ // 15 tool calls * 500 tokens = 7500 → over trigger
93
+ // Pruning protects last 40K (all of them since total < 40K)
94
+ // We need pruneProtect to be smaller to actually see pruning
95
+ const pruneConfig = {
96
+ contextWindow: 8000,
97
+ maxOutputTokens: 800,
98
+ pruneProtect: 1500, // only protect last 1500 tokens
99
+ pruneMinimum: 500, // prune if we can reclaim 500+
100
+ };
101
+ const messages = buildLongConversation(15, 500);
102
+ const tokensBefore = estimateMessagesTokens(messages);
103
+ expect(tokensBefore).toBeGreaterThan(6120);
104
+ const pruneSummarize = vi.fn(async () => "Should not be called");
105
+ const result = await compactIfNeeded({
106
+ systemPrompt: "You are an agent.",
107
+ messages,
108
+ config: pruneConfig,
109
+ summarize: pruneSummarize,
110
+ mode: "task",
111
+ });
112
+ expect(result.compacted).toBe(true);
113
+ expect(result.pruned).toBe(true);
114
+ expect(result.tokensBefore).toBeGreaterThan(result.tokensAfter);
115
+ // Verify some tool outputs were pruned (contain placeholder text)
116
+ const allText = JSON.stringify(result.messages);
117
+ expect(allText).toContain("Output pruned");
118
+ });
119
+ it("calls summarize when pruning alone isn't enough", async () => {
120
+ // Many tool calls with large outputs — pruning won't bring it under target
121
+ const messages = buildLongConversation(20, 300);
122
+ const mockSummarize = vi.fn(async () => {
123
+ return "Summary: Fixed auth bug. Modified auth.ts and login.ts.";
124
+ });
125
+ const result = await compactIfNeeded({
126
+ systemPrompt: "You are an agent.",
127
+ messages,
128
+ config: smallConfig,
129
+ summarize: mockSummarize,
130
+ mode: "task",
131
+ });
132
+ expect(result.compacted).toBe(true);
133
+ expect(mockSummarize).toHaveBeenCalled();
134
+ expect(result.summary).toBeDefined();
135
+ expect(result.summary).toContain("Fixed auth bug");
136
+ // First message should be the summary injection
137
+ const firstMsg = result.messages[0];
138
+ expect(firstMsg.role).toBe("user");
139
+ expect(firstMsg.content).toContain("Previous context summary");
140
+ expect(firstMsg.content).toContain("Fixed auth bug");
141
+ // Token count should be significantly reduced
142
+ expect(result.tokensAfter).toBeLessThan(result.tokensBefore);
143
+ });
144
+ it("preserves recent messages after summarization", async () => {
145
+ const messages = buildLongConversation(20, 300);
146
+ const lastAssistantMsg = messages[messages.length - 1];
147
+ const mockSummarize = vi.fn(async () => "Summary of old conversation.");
148
+ const result = await compactIfNeeded({
149
+ systemPrompt: "You are an agent.",
150
+ messages,
151
+ config: smallConfig,
152
+ summarize: mockSummarize,
153
+ mode: "task",
154
+ });
155
+ // The last message (assistant analysis) should still be in the result
156
+ const resultTexts = result.messages.map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content));
157
+ const lastOriginalText = typeof lastAssistantMsg.content === "string"
158
+ ? lastAssistantMsg.content
159
+ : JSON.stringify(lastAssistantMsg.content);
160
+ expect(resultTexts.some((t) => t.includes("reviewed all the files"))).toBe(true);
161
+ });
162
+ it("uses task prompt for task mode", async () => {
163
+ const messages = buildLongConversation(20, 300);
164
+ let capturedPrompt = "";
165
+ const mockSummarize = vi.fn(async (_msgs, prompt) => {
166
+ capturedPrompt = prompt;
167
+ return "Summary";
168
+ });
169
+ await compactIfNeeded({
170
+ systemPrompt: "You are an agent.",
171
+ messages,
172
+ config: smallConfig,
173
+ summarize: mockSummarize,
174
+ mode: "task",
175
+ });
176
+ expect(capturedPrompt).toContain("Goal");
177
+ expect(capturedPrompt).toContain("Progress");
178
+ expect(capturedPrompt).toContain("Files");
179
+ expect(capturedPrompt).toContain("Next Steps");
180
+ });
181
+ it("uses chat prompt for chat mode", async () => {
182
+ const messages = buildLongConversation(20, 300);
183
+ let capturedPrompt = "";
184
+ const mockSummarize = vi.fn(async (_msgs, prompt) => {
185
+ capturedPrompt = prompt;
186
+ return "Summary";
187
+ });
188
+ await compactIfNeeded({
189
+ systemPrompt: "You are an agent.",
190
+ messages,
191
+ config: smallConfig,
192
+ summarize: mockSummarize,
193
+ mode: "chat",
194
+ });
195
+ expect(capturedPrompt).toContain("preferences");
196
+ expect(capturedPrompt).toContain("decisions");
197
+ });
198
+ it("passes older messages to summarize, not recent ones", async () => {
199
+ const messages = buildLongConversation(20, 300);
200
+ let summarizedCount = 0;
201
+ const mockSummarize = vi.fn(async (msgs) => {
202
+ summarizedCount = msgs.length;
203
+ return "Summary";
204
+ });
205
+ const result = await compactIfNeeded({
206
+ systemPrompt: "You are an agent.",
207
+ messages,
208
+ config: smallConfig,
209
+ summarize: mockSummarize,
210
+ mode: "task",
211
+ });
212
+ // Should have summarized some but not all messages
213
+ expect(summarizedCount).toBeGreaterThan(0);
214
+ expect(summarizedCount).toBeLessThan(messages.length);
215
+ // Result should have fewer messages than original
216
+ expect(result.messages.length).toBeLessThan(messages.length);
217
+ });
218
+ it("handles conversation with only text (no tool calls)", async () => {
219
+ // Build a text-heavy conversation that exceeds context
220
+ const messages = [];
221
+ for (let i = 0; i < 30; i++) {
222
+ messages.push(userMsg(generateLargeText(50)));
223
+ messages.push(assistantTextMsg(generateLargeText(50)));
224
+ }
225
+ const mockSummarize = vi.fn(async () => "Summary of long text conversation.");
226
+ const result = await compactIfNeeded({
227
+ systemPrompt: "You are an agent.",
228
+ messages,
229
+ config: smallConfig,
230
+ summarize: mockSummarize,
231
+ mode: "chat",
232
+ });
233
+ expect(result.compacted).toBe(true);
234
+ // With no tool outputs to prune, should go straight to summarization
235
+ expect(mockSummarize).toHaveBeenCalled();
236
+ });
237
+ it("respects disabled flag", async () => {
238
+ const messages = buildLongConversation(20, 300);
239
+ const result = await compactIfNeeded({
240
+ systemPrompt: "You are an agent.",
241
+ messages,
242
+ config: { ...smallConfig, disabled: true },
243
+ summarize: summarizeFn,
244
+ mode: "task",
245
+ });
246
+ expect(result.compacted).toBe(false);
247
+ expect(result.messages).toEqual(messages);
248
+ });
249
+ it("multiple compaction cycles work correctly", async () => {
250
+ // Simulate: first compaction, then more messages, then second compaction
251
+ const messages1 = buildLongConversation(15, 300);
252
+ const mockSummarize = vi.fn(async () => "Summary round 1.");
253
+ const result1 = await compactIfNeeded({
254
+ systemPrompt: "You are an agent.",
255
+ messages: messages1,
256
+ config: smallConfig,
257
+ summarize: mockSummarize,
258
+ mode: "task",
259
+ });
260
+ expect(result1.compacted).toBe(true);
261
+ // Add more messages on top of compacted result
262
+ const extendedMessages = [
263
+ ...result1.messages,
264
+ ...buildLongConversation(10, 300).slice(1), // skip the initial user msg
265
+ ];
266
+ mockSummarize.mockResolvedValueOnce("Summary round 2 (includes round 1).");
267
+ const result2 = await compactIfNeeded({
268
+ systemPrompt: "You are an agent.",
269
+ messages: extendedMessages,
270
+ config: smallConfig,
271
+ summarize: mockSummarize,
272
+ mode: "task",
273
+ });
274
+ expect(result2.compacted).toBe(true);
275
+ // The summary should be from round 2
276
+ if (result2.summary) {
277
+ expect(result2.summary).toContain("round 2");
278
+ }
279
+ });
280
+ });
281
+ //# sourceMappingURL=context-compaction-e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compaction-e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/context-compaction-e2e.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context-compactor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compactor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/context-compactor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,338 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { estimateTokens, estimateMessagesTokens, shouldCompact, pruneToolOutputs, compactIfNeeded, getCompactionPrompt, PRUNE_PROTECT, PRUNE_MINIMUM, TRIGGER_THRESHOLD, TARGET_AFTER, } from "../context-compactor.js";
3
+ // ── estimateTokens ──────────────────────────────────────────────────────
4
+ describe("estimateTokens", () => {
5
+ it("returns 0 for empty string", () => {
6
+ expect(estimateTokens("")).toBe(0);
7
+ });
8
+ it('estimates "hello" as ~1-2 tokens', () => {
9
+ // "hello" = 5 chars => 5/4 = 1.25 => Math.round => 1
10
+ expect(estimateTokens("hello")).toBe(1);
11
+ });
12
+ it("estimates longer text proportionally", () => {
13
+ const text = "a".repeat(400);
14
+ expect(estimateTokens(text)).toBe(100);
15
+ });
16
+ });
17
+ // ── estimateMessagesTokens ──────────────────────────────────────────────
18
+ describe("estimateMessagesTokens", () => {
19
+ it("estimates string content messages", () => {
20
+ const messages = [
21
+ { role: "user", content: "Hello, how are you?" },
22
+ { role: "assistant", content: "I am fine." },
23
+ ];
24
+ const tokens = estimateMessagesTokens(messages);
25
+ // Each message: 4 (overhead) + text tokens
26
+ // "Hello, how are you?" = 19 chars => ~5 tokens => total 9
27
+ // "I am fine." = 10 chars => ~3 tokens => total 7
28
+ // Sum = 16
29
+ expect(tokens).toBeGreaterThan(0);
30
+ expect(tokens).toBe(4 + estimateTokens("Hello, how are you?") +
31
+ 4 + estimateTokens("I am fine."));
32
+ });
33
+ it("estimates tool call messages", () => {
34
+ const messages = [
35
+ {
36
+ role: "assistant",
37
+ content: [
38
+ {
39
+ type: "toolCall",
40
+ id: "call_1",
41
+ name: "readFile",
42
+ arguments: { path: "/tmp/test.txt" },
43
+ },
44
+ ],
45
+ },
46
+ ];
47
+ const tokens = estimateMessagesTokens(messages);
48
+ expect(tokens).toBeGreaterThan(4); // more than just overhead
49
+ });
50
+ it("estimates tool result messages", () => {
51
+ const messages = [
52
+ {
53
+ role: "toolResult",
54
+ content: [
55
+ { type: "text", text: "File contents here: hello world" },
56
+ ],
57
+ },
58
+ ];
59
+ const tokens = estimateMessagesTokens(messages);
60
+ expect(tokens).toBe(4 + estimateTokens("File contents here: hello world"));
61
+ });
62
+ it("handles empty array", () => {
63
+ expect(estimateMessagesTokens([])).toBe(0);
64
+ });
65
+ });
66
+ // ── shouldCompact ───────────────────────────────────────────────────────
67
+ describe("shouldCompact", () => {
68
+ const config = {
69
+ contextWindow: 100_000,
70
+ maxOutputTokens: 4_000,
71
+ };
72
+ it("returns false when below threshold", () => {
73
+ // usable = 96000, threshold = 81600 (85%)
74
+ expect(shouldCompact(config, 50_000)).toBe(false);
75
+ });
76
+ it("returns true when above threshold", () => {
77
+ // usable = 96000, threshold = 81600 (85%)
78
+ expect(shouldCompact(config, 85_000)).toBe(true);
79
+ });
80
+ it("returns true at exact threshold", () => {
81
+ const usable = config.contextWindow - config.maxOutputTokens;
82
+ const threshold = usable * TRIGGER_THRESHOLD;
83
+ expect(shouldCompact(config, threshold)).toBe(true);
84
+ });
85
+ it("returns false when disabled", () => {
86
+ expect(shouldCompact({ ...config, disabled: true }, 999_999)).toBe(false);
87
+ });
88
+ it("uses custom triggerThreshold", () => {
89
+ const custom = {
90
+ ...config,
91
+ triggerThreshold: 0.5,
92
+ };
93
+ // usable = 96000, threshold = 48000 (50%)
94
+ expect(shouldCompact(custom, 50_000)).toBe(true);
95
+ expect(shouldCompact(custom, 40_000)).toBe(false);
96
+ });
97
+ });
98
+ // ── pruneToolOutputs ────────────────────────────────────────────────────
99
+ describe("pruneToolOutputs", () => {
100
+ const config = {
101
+ contextWindow: 200_000,
102
+ maxOutputTokens: 4_000,
103
+ };
104
+ it("returns unchanged messages when no tool outputs", () => {
105
+ const messages = [
106
+ { role: "user", content: "hello" },
107
+ { role: "assistant", content: "hi" },
108
+ ];
109
+ const result = pruneToolOutputs(messages, config);
110
+ expect(result).toEqual(messages);
111
+ });
112
+ it("protects recent tool outputs", () => {
113
+ // Create a single tool result that fits within PRUNE_PROTECT
114
+ const smallOutput = "x".repeat(100); // ~25 tokens, well within 40K
115
+ const messages = [
116
+ {
117
+ role: "toolResult",
118
+ content: [{ type: "text", text: smallOutput }],
119
+ toolName: "readFile",
120
+ },
121
+ ];
122
+ const result = pruneToolOutputs(messages, config);
123
+ // Should be unchanged since it's within the protected window
124
+ expect(result[0].content[0].text).toBe(smallOutput);
125
+ });
126
+ it("prunes old outputs beyond the protection window", () => {
127
+ // Create outputs that exceed PRUNE_PROTECT + PRUNE_MINIMUM
128
+ const bigOutput = "x".repeat(PRUNE_PROTECT * 4 + PRUNE_MINIMUM * 4 + 100);
129
+ // Oldest message — should be pruned
130
+ const messages = [
131
+ {
132
+ role: "toolResult",
133
+ content: [{ type: "text", text: bigOutput }],
134
+ toolName: "oldTool",
135
+ },
136
+ // Recent message — protected
137
+ {
138
+ role: "toolResult",
139
+ content: [{ type: "text", text: "y".repeat(PRUNE_PROTECT * 4) }],
140
+ toolName: "recentTool",
141
+ },
142
+ ];
143
+ const result = pruneToolOutputs(messages, config);
144
+ // Old output should be pruned
145
+ expect(result[0].content[0].text).toContain("[Output pruned");
146
+ expect(result[0].content[0].text).toContain("Tool: oldTool");
147
+ // Recent output should be preserved
148
+ expect(result[1].content[0].text).toBe("y".repeat(PRUNE_PROTECT * 4));
149
+ });
150
+ it("does not prune if total prunable is under minimum", () => {
151
+ // Two small outputs — neither exceeds minimum for pruning
152
+ const smallOutput = "x".repeat(100);
153
+ const messages = [
154
+ {
155
+ role: "toolResult",
156
+ content: [{ type: "text", text: smallOutput }],
157
+ toolName: "tool1",
158
+ },
159
+ {
160
+ role: "toolResult",
161
+ content: [{ type: "text", text: smallOutput }],
162
+ toolName: "tool2",
163
+ },
164
+ ];
165
+ const result = pruneToolOutputs(messages, config);
166
+ // Both should be unchanged
167
+ expect(result[0].content[0].text).toBe(smallOutput);
168
+ expect(result[1].content[0].text).toBe(smallOutput);
169
+ });
170
+ it("does not mutate original messages", () => {
171
+ const bigOutput = "x".repeat((PRUNE_PROTECT + PRUNE_MINIMUM) * 4 + 400);
172
+ const messages = [
173
+ {
174
+ role: "toolResult",
175
+ content: [{ type: "text", text: bigOutput }],
176
+ toolName: "tool1",
177
+ },
178
+ {
179
+ role: "toolResult",
180
+ content: [{ type: "text", text: "y".repeat(PRUNE_PROTECT * 4) }],
181
+ toolName: "tool2",
182
+ },
183
+ ];
184
+ const originalFirstText = messages[0].content[0].text;
185
+ pruneToolOutputs(messages, config);
186
+ // Original should be untouched
187
+ expect(messages[0].content[0].text).toBe(originalFirstText);
188
+ });
189
+ });
190
+ // ── compactIfNeeded ─────────────────────────────────────────────────────
191
+ describe("compactIfNeeded", () => {
192
+ const smallConfig = {
193
+ contextWindow: 1000,
194
+ maxOutputTokens: 100,
195
+ // usable = 900, trigger at 765 (85%), target after = 450 (50%)
196
+ };
197
+ const mockSummarize = vi.fn(async () => "Summary of previous context.");
198
+ function makeInput(overrides = {}) {
199
+ return {
200
+ systemPrompt: "You are a helpful assistant.",
201
+ messages: [
202
+ { role: "user", content: "hello" },
203
+ { role: "assistant", content: "hi there" },
204
+ ],
205
+ config: smallConfig,
206
+ summarize: mockSummarize,
207
+ mode: "task",
208
+ ...overrides,
209
+ };
210
+ }
211
+ it("returns unchanged when under threshold", async () => {
212
+ const input = makeInput({
213
+ config: {
214
+ contextWindow: 1_000_000,
215
+ maxOutputTokens: 4_000,
216
+ },
217
+ });
218
+ const result = await compactIfNeeded(input);
219
+ expect(result.compacted).toBe(false);
220
+ expect(result.pruned).toBe(false);
221
+ expect(result.messages).toBe(input.messages); // same reference
222
+ expect(result.tokensBefore).toBe(result.tokensAfter);
223
+ });
224
+ it("returns unchanged when disabled", async () => {
225
+ const input = makeInput({
226
+ config: { ...smallConfig, disabled: true },
227
+ });
228
+ // Even with a tiny context window, disabled means no compaction
229
+ const result = await compactIfNeeded(input);
230
+ expect(result.compacted).toBe(false);
231
+ });
232
+ it("prunes sufficiently without calling summarize", async () => {
233
+ const summarize = vi.fn(async () => "summary");
234
+ // Build messages that push over threshold due to large tool outputs,
235
+ // where pruning alone is enough to get under target.
236
+ //
237
+ // The pruning logic walks backwards (most recent first) and protects
238
+ // recent tool outputs up to pruneProtect tokens. Once that budget is
239
+ // filled, everything older is prunable.
240
+ //
241
+ // Config: contextWindow=2000, maxOutputTokens=100 => usable=1900
242
+ // trigger = 1615 (85%), target = 950 (50%)
243
+ const config = {
244
+ contextWindow: 2000,
245
+ maxOutputTokens: 100,
246
+ pruneProtect: 200, // protect 200 tokens of recent tool output
247
+ pruneMinimum: 50, // prune if at least 50 tokens reclaimable
248
+ };
249
+ // System prompt "You are a helpful assistant." = 28 chars = 7 tokens
250
+ // Msg "do something" = 12 chars = 3 + 4 overhead = 7
251
+ // Old tool result "x"*6000 = 6000 chars = 1500 + 4 overhead = 1504 tokens
252
+ // Recent tool result "y"*1000 = 1000 chars = 250 + 4 overhead = 254 tokens
253
+ // (250 > pruneProtect=200, so this alone fills the protect budget)
254
+ // Msg "done" = 4 chars = 1 + 4 overhead = 5
255
+ // Total = 7 + 7 + 1504 + 254 + 5 = 1777, well over trigger of 1615
256
+ //
257
+ // After pruning: old tool result becomes short placeholder (~15 tokens + overhead)
258
+ // Total after = 7 + 7 + ~19 + 254 + 5 = ~292, under target of 950
259
+ const messages = [
260
+ { role: "user", content: "do something" },
261
+ {
262
+ role: "toolResult",
263
+ content: [{ type: "text", text: "x".repeat(6000) }],
264
+ toolName: "bigTool",
265
+ },
266
+ {
267
+ role: "toolResult",
268
+ content: [{ type: "text", text: "y".repeat(1000) }],
269
+ toolName: "recentTool",
270
+ },
271
+ { role: "assistant", content: "done" },
272
+ ];
273
+ const input = makeInput({ messages, config, summarize });
274
+ const result = await compactIfNeeded(input);
275
+ expect(result.pruned).toBe(true);
276
+ expect(result.compacted).toBe(true);
277
+ expect(result.tokensAfter).toBeLessThan(result.tokensBefore);
278
+ // Summarize should NOT have been called since pruning was sufficient
279
+ expect(summarize).not.toHaveBeenCalled();
280
+ // The old tool result should contain the placeholder
281
+ const prunedToolResult = result.messages[1];
282
+ expect(prunedToolResult.content[0].text).toContain("[Output pruned");
283
+ });
284
+ it("calls summarize when pruning is insufficient", async () => {
285
+ const summarize = vi.fn(async () => "Brief summary.");
286
+ // Very small context window forces summarization
287
+ const config = {
288
+ contextWindow: 200,
289
+ maxOutputTokens: 10,
290
+ // usable = 190, trigger = 161.5, target = 95
291
+ pruneProtect: 0,
292
+ pruneMinimum: 0,
293
+ };
294
+ // Generate enough text messages to exceed threshold
295
+ // Each message ~4 overhead + text tokens
296
+ const messages = [];
297
+ for (let i = 0; i < 30; i++) {
298
+ messages.push({ role: "user", content: `Question number ${i}: ${"context ".repeat(10)}` });
299
+ messages.push({ role: "assistant", content: `Answer number ${i}: ${"response ".repeat(10)}` });
300
+ }
301
+ const input = makeInput({ messages, config, summarize });
302
+ const result = await compactIfNeeded(input);
303
+ expect(result.compacted).toBe(true);
304
+ expect(summarize).toHaveBeenCalled();
305
+ expect(result.summary).toBe("Brief summary.");
306
+ // First message should be the summary injection
307
+ expect(result.messages[0].role).toBe("user");
308
+ expect(result.messages[0].content).toContain("[Previous context summary]");
309
+ expect(result.messages[0].content).toContain("Brief summary.");
310
+ expect(result.messages[0].content).toContain("[End summary — continue from here]");
311
+ });
312
+ });
313
+ // ── getCompactionPrompt ─────────────────────────────────────────────────
314
+ describe("getCompactionPrompt", () => {
315
+ it("returns task prompt for task mode", () => {
316
+ const prompt = getCompactionPrompt("task");
317
+ expect(prompt).toContain("Summarize the conversation for handoff");
318
+ expect(prompt).toContain("### Goal");
319
+ expect(prompt).toContain("### Progress");
320
+ expect(prompt).toContain("### Files Modified");
321
+ expect(prompt).toContain("### Next Steps");
322
+ });
323
+ it("returns chat prompt for chat mode", () => {
324
+ const prompt = getCompactionPrompt("chat");
325
+ expect(prompt).toContain("Summarize the conversation to preserve context");
326
+ expect(prompt).toContain("user preferences");
327
+ });
328
+ });
329
+ // ── Constants exported correctly ────────────────────────────────────────
330
+ describe("constants", () => {
331
+ it("exports expected values", () => {
332
+ expect(PRUNE_PROTECT).toBe(40_000);
333
+ expect(PRUNE_MINIMUM).toBe(20_000);
334
+ expect(TRIGGER_THRESHOLD).toBe(0.85);
335
+ expect(TARGET_AFTER).toBe(0.50);
336
+ });
337
+ });
338
+ //# sourceMappingURL=context-compactor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compactor.test.js","sourceRoot":"","sources":["../../src/__tests__/context-compactor.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Context Compaction — prevents context window overflow.
3
+ *
4
+ * Two-phase approach (same as OpenCode):
5
+ * Phase 1: Prune old tool outputs (no LLM call, free)
6
+ * Phase 2: LLM summarization if pruning isn't enough
7
+ */
8
+ /** Estimate token count: ~4 chars per token (industry standard heuristic) */
9
+ export declare function estimateTokens(text: string): number;
10
+ /** Protect last 40K tokens of tool output from pruning */
11
+ export declare const PRUNE_PROTECT = 40000;
12
+ /** Only prune if we can reclaim at least 20K tokens */
13
+ export declare const PRUNE_MINIMUM = 20000;
14
+ /** Trigger compaction at 85% of usable context */
15
+ export declare const TRIGGER_THRESHOLD = 0.85;
16
+ /** Target 50% of usable context after compaction */
17
+ export declare const TARGET_AFTER = 0.5;
18
+ export interface CompactionConfig {
19
+ /** Model's context window size in tokens */
20
+ contextWindow: number;
21
+ /** Max output tokens the model can generate */
22
+ maxOutputTokens: number;
23
+ /** Trigger compaction at this % of usable context (default: 0.85) */
24
+ triggerThreshold?: number;
25
+ /** Tokens of recent tool output to protect from pruning (default: 40000) */
26
+ pruneProtect?: number;
27
+ /** Minimum tokens to reclaim before pruning (default: 20000) */
28
+ pruneMinimum?: number;
29
+ /** Disable auto-compaction entirely */
30
+ disabled?: boolean;
31
+ }
32
+ export type SummarizeFn = (messages: any[], prompt: string) => Promise<string>;
33
+ /** Event emitted when compaction occurs */
34
+ export interface CompactionEvent {
35
+ /** What phase triggered: "prune" or "summarize" */
36
+ phase: "prune" | "summarize";
37
+ /** Token count before compaction */
38
+ tokensBefore: number;
39
+ /** Token count after compaction */
40
+ tokensAfter: number;
41
+ /** Tokens reclaimed */
42
+ tokensReclaimed: number;
43
+ /** Number of messages before */
44
+ messagesBefore: number;
45
+ /** Number of messages after */
46
+ messagesAfter: number;
47
+ /** Number of tool outputs pruned (phase 1) */
48
+ toolOutputsPruned?: number;
49
+ /** Summary text (phase 2 only) */
50
+ summary?: string;
51
+ /** Mode: task or chat */
52
+ mode: "task" | "chat";
53
+ }
54
+ export type OnCompactionFn = (event: CompactionEvent) => void;
55
+ export interface CompactionInput {
56
+ systemPrompt: string;
57
+ messages: any[];
58
+ tools?: any[];
59
+ config: CompactionConfig;
60
+ summarize: SummarizeFn;
61
+ mode: "task" | "chat";
62
+ /** Called when compaction occurs — use for logging, events, UI updates */
63
+ onCompaction?: OnCompactionFn;
64
+ }
65
+ export interface CompactionResult {
66
+ messages: any[];
67
+ compacted: boolean;
68
+ pruned: boolean;
69
+ summary?: string;
70
+ tokensBefore: number;
71
+ tokensAfter: number;
72
+ }
73
+ /** Get the appropriate compaction prompt for a given mode */
74
+ export declare function getCompactionPrompt(mode: "task" | "chat"): string;
75
+ /** Estimate total tokens across an array of messages */
76
+ export declare function estimateMessagesTokens(messages: any[]): number;
77
+ /** Check whether compaction should be triggered based on current token usage */
78
+ export declare function shouldCompact(config: CompactionConfig, currentTokens: number): boolean;
79
+ /**
80
+ * Walk backwards through messages and replace old tool result content with
81
+ * placeholders. Protects the most recent `pruneProtect` tokens of tool output.
82
+ * Only prunes if total prunable tokens >= `pruneMinimum`.
83
+ */
84
+ export declare function pruneToolOutputs(messages: any[], config: CompactionConfig): any[];
85
+ /**
86
+ * Main entry point. Checks if compaction is needed, tries pruning first,
87
+ * then falls back to LLM summarization if necessary.
88
+ */
89
+ export declare function compactIfNeeded(input: CompactionInput): Promise<CompactionResult>;
90
+ //# sourceMappingURL=context-compactor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compactor.d.ts","sourceRoot":"","sources":["../src/context-compactor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAID,0DAA0D;AAC1D,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC,uDAAuD;AACvD,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC,kDAAkD;AAClD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,oDAAoD;AACpD,eAAO,MAAM,YAAY,MAAO,CAAC;AAIjC,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE/E,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC;IAC7B,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,GAAG,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,WAAW,CAAC;IACvB,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,cAAc,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,GAAG,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AA+BD,6DAA6D;AAC7D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEjE;AAID,wDAAwD;AACxD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,MAAM,CAM9D;AAkCD,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAKtF;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,GAAG,EAAE,EACf,MAAM,EAAE,gBAAgB,GACvB,GAAG,EAAE,CAqEP;AAID;;;GAGG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsHvF"}
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Context Compaction — prevents context window overflow.
3
+ *
4
+ * Two-phase approach (same as OpenCode):
5
+ * Phase 1: Prune old tool outputs (no LLM call, free)
6
+ * Phase 2: LLM summarization if pruning isn't enough
7
+ */
8
+ // ── Token estimation ────────────────────────────────────────────────────
9
+ /** Estimate token count: ~4 chars per token (industry standard heuristic) */
10
+ export function estimateTokens(text) {
11
+ return Math.round(text.length / 4);
12
+ }
13
+ // ── Constants (same as OpenCode) ────────────────────────────────────────
14
+ /** Protect last 40K tokens of tool output from pruning */
15
+ export const PRUNE_PROTECT = 40_000;
16
+ /** Only prune if we can reclaim at least 20K tokens */
17
+ export const PRUNE_MINIMUM = 20_000;
18
+ /** Trigger compaction at 85% of usable context */
19
+ export const TRIGGER_THRESHOLD = 0.85;
20
+ /** Target 50% of usable context after compaction */
21
+ export const TARGET_AFTER = 0.50;
22
+ // ── Summarization prompts ───────────────────────────────────────────────
23
+ const TASK_COMPACTION_PROMPT = `Summarize the conversation for handoff to a continuation agent.
24
+
25
+ ## Required sections:
26
+ ### Goal
27
+ What is the task trying to accomplish?
28
+
29
+ ### Progress
30
+ - Completed: what's done
31
+ - In progress: what was interrupted
32
+ - Remaining: what's left
33
+
34
+ ### Key Decisions & Discoveries
35
+ Technical decisions, constraints found, approaches tried and failed
36
+
37
+ ### Files Modified
38
+ List of files read, created, or edited with brief description
39
+
40
+ ### Next Steps
41
+ Specific actions to take next
42
+
43
+ Be precise and concise. Preserve file paths, function names, and error messages exactly.`;
44
+ const CHAT_COMPACTION_PROMPT = `Summarize the conversation to preserve context for continuation.
45
+ Focus on: user preferences, decisions made, questions asked, key facts shared.
46
+ Preserve any code snippets, file paths, or technical details mentioned.
47
+ Be precise and concise.`;
48
+ /** Get the appropriate compaction prompt for a given mode */
49
+ export function getCompactionPrompt(mode) {
50
+ return mode === "task" ? TASK_COMPACTION_PROMPT : CHAT_COMPACTION_PROMPT;
51
+ }
52
+ // ── Token estimation for messages ───────────────────────────────────────
53
+ /** Estimate total tokens across an array of messages */
54
+ export function estimateMessagesTokens(messages) {
55
+ let total = 0;
56
+ for (const msg of messages) {
57
+ total += estimateMessageTokens(msg);
58
+ }
59
+ return total;
60
+ }
61
+ function estimateMessageTokens(msg) {
62
+ if (!msg)
63
+ return 0;
64
+ // Role overhead (~4 tokens for role + formatting)
65
+ let tokens = 4;
66
+ const content = msg.content;
67
+ if (typeof content === "string") {
68
+ tokens += estimateTokens(content);
69
+ }
70
+ else if (Array.isArray(content)) {
71
+ for (const block of content) {
72
+ if (block.type === "text" && typeof block.text === "string") {
73
+ tokens += estimateTokens(block.text);
74
+ }
75
+ else if (block.type === "toolCall") {
76
+ // Tool call: name + stringified arguments
77
+ if (block.name)
78
+ tokens += estimateTokens(block.name);
79
+ if (block.arguments !== undefined) {
80
+ const args = typeof block.arguments === "string"
81
+ ? block.arguments
82
+ : JSON.stringify(block.arguments);
83
+ tokens += estimateTokens(args);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ return tokens;
89
+ }
90
+ // ── Threshold check ─────────────────────────────────────────────────────
91
+ /** Check whether compaction should be triggered based on current token usage */
92
+ export function shouldCompact(config, currentTokens) {
93
+ if (config.disabled)
94
+ return false;
95
+ const usable = config.contextWindow - config.maxOutputTokens;
96
+ const threshold = config.triggerThreshold ?? TRIGGER_THRESHOLD;
97
+ return currentTokens >= usable * threshold;
98
+ }
99
+ // ── Phase 1: Prune tool outputs ─────────────────────────────────────────
100
+ /**
101
+ * Walk backwards through messages and replace old tool result content with
102
+ * placeholders. Protects the most recent `pruneProtect` tokens of tool output.
103
+ * Only prunes if total prunable tokens >= `pruneMinimum`.
104
+ */
105
+ export function pruneToolOutputs(messages, config) {
106
+ const protectTokens = config.pruneProtect ?? PRUNE_PROTECT;
107
+ const minimumTokens = config.pruneMinimum ?? PRUNE_MINIMUM;
108
+ const entries = [];
109
+ for (let i = messages.length - 1; i >= 0; i--) {
110
+ const msg = messages[i];
111
+ if (msg.role === "toolResult" && Array.isArray(msg.content)) {
112
+ for (let j = msg.content.length - 1; j >= 0; j--) {
113
+ const block = msg.content[j];
114
+ if (block.type === "text" && typeof block.text === "string") {
115
+ const tokens = estimateTokens(block.text);
116
+ // Try to find the tool name from the message-level or block-level properties
117
+ const toolName = msg.toolName || msg.name || block.toolName || "unknown";
118
+ entries.push({
119
+ messageIndex: i,
120
+ blockIndex: j,
121
+ tokens,
122
+ toolName,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ }
128
+ // entries are in reverse order (most recent first) due to backward walk
129
+ // Calculate how many tokens to protect and how many are prunable
130
+ let protectedSoFar = 0;
131
+ let prunableTokens = 0;
132
+ const prunableEntries = [];
133
+ for (const entry of entries) {
134
+ if (protectedSoFar < protectTokens) {
135
+ protectedSoFar += entry.tokens;
136
+ }
137
+ else {
138
+ prunableTokens += entry.tokens;
139
+ prunableEntries.push(entry);
140
+ }
141
+ }
142
+ // Only prune if we can reclaim enough
143
+ if (prunableTokens < minimumTokens) {
144
+ return messages;
145
+ }
146
+ // Deep-clone messages to avoid mutation
147
+ const result = JSON.parse(JSON.stringify(messages));
148
+ // Apply pruning
149
+ for (const entry of prunableEntries) {
150
+ const msg = result[entry.messageIndex];
151
+ if (entry.blockIndex !== undefined && Array.isArray(msg.content)) {
152
+ msg.content[entry.blockIndex] = {
153
+ type: "text",
154
+ text: `[Output pruned — was ${entry.tokens} tokens. Tool: ${entry.toolName}]`,
155
+ };
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ // ── Phase 2: Full compaction ────────────────────────────────────────────
161
+ /**
162
+ * Main entry point. Checks if compaction is needed, tries pruning first,
163
+ * then falls back to LLM summarization if necessary.
164
+ */
165
+ export async function compactIfNeeded(input) {
166
+ const { systemPrompt, messages, tools, config, summarize, mode, onCompaction } = input;
167
+ if (config.disabled) {
168
+ const tokens = estimateTotalTokens(systemPrompt, messages, tools);
169
+ return {
170
+ messages,
171
+ compacted: false,
172
+ pruned: false,
173
+ tokensBefore: tokens,
174
+ tokensAfter: tokens,
175
+ };
176
+ }
177
+ const tokensBefore = estimateTotalTokens(systemPrompt, messages, tools);
178
+ const usable = config.contextWindow - config.maxOutputTokens;
179
+ const threshold = config.triggerThreshold ?? TRIGGER_THRESHOLD;
180
+ // Not over threshold — nothing to do
181
+ if (tokensBefore < usable * threshold) {
182
+ return {
183
+ messages,
184
+ compacted: false,
185
+ pruned: false,
186
+ tokensBefore,
187
+ tokensAfter: tokensBefore,
188
+ };
189
+ }
190
+ const target = usable * TARGET_AFTER;
191
+ // Phase 1: try pruning tool outputs
192
+ const pruned = pruneToolOutputs(messages, config);
193
+ const tokensAfterPrune = estimateTotalTokens(systemPrompt, pruned, tools);
194
+ if (tokensAfterPrune <= target) {
195
+ onCompaction?.({
196
+ phase: "prune",
197
+ tokensBefore,
198
+ tokensAfter: tokensAfterPrune,
199
+ tokensReclaimed: tokensBefore - tokensAfterPrune,
200
+ messagesBefore: messages.length,
201
+ messagesAfter: pruned.length,
202
+ toolOutputsPruned: countPrunedOutputs(pruned),
203
+ mode,
204
+ });
205
+ return {
206
+ messages: pruned,
207
+ compacted: true,
208
+ pruned: true,
209
+ tokensBefore,
210
+ tokensAfter: tokensAfterPrune,
211
+ };
212
+ }
213
+ // Phase 2: LLM summarization
214
+ // Keep ~60% of target budget as recent messages
215
+ const recentBudget = target * 0.6;
216
+ // Walk backwards to find split point
217
+ let recentTokens = 0;
218
+ let splitIndex = pruned.length;
219
+ for (let i = pruned.length - 1; i >= 0; i--) {
220
+ const msgTokens = estimateMessageTokens(pruned[i]);
221
+ if (recentTokens + msgTokens > recentBudget && i < pruned.length - 1) {
222
+ splitIndex = i + 1;
223
+ break;
224
+ }
225
+ recentTokens += msgTokens;
226
+ if (i === 0)
227
+ splitIndex = 0;
228
+ }
229
+ // Need at least some older messages to summarize
230
+ if (splitIndex <= 0) {
231
+ // Everything is "recent" — just return pruned
232
+ return {
233
+ messages: pruned,
234
+ compacted: true,
235
+ pruned: true,
236
+ tokensBefore,
237
+ tokensAfter: tokensAfterPrune,
238
+ };
239
+ }
240
+ const olderMessages = pruned.slice(0, splitIndex);
241
+ const recentMessages = pruned.slice(splitIndex);
242
+ const prompt = getCompactionPrompt(mode);
243
+ const summary = await summarize(olderMessages, prompt);
244
+ const summaryMessage = {
245
+ role: "user",
246
+ content: `[Previous context summary]\n${summary}\n[End summary — continue from here]`,
247
+ };
248
+ const compactedMessages = [summaryMessage, ...recentMessages];
249
+ const tokensAfter = estimateTotalTokens(systemPrompt, compactedMessages, tools);
250
+ onCompaction?.({
251
+ phase: "summarize",
252
+ tokensBefore,
253
+ tokensAfter,
254
+ tokensReclaimed: tokensBefore - tokensAfter,
255
+ messagesBefore: messages.length,
256
+ messagesAfter: compactedMessages.length,
257
+ toolOutputsPruned: countPrunedOutputs(pruned),
258
+ summary,
259
+ mode,
260
+ });
261
+ return {
262
+ messages: compactedMessages,
263
+ compacted: true,
264
+ pruned: true,
265
+ summary,
266
+ tokensBefore,
267
+ tokensAfter,
268
+ };
269
+ }
270
+ // ── Internal helpers ────────────────────────────────────────────────────
271
+ function countPrunedOutputs(messages) {
272
+ let count = 0;
273
+ for (const msg of messages) {
274
+ if (msg.role === "toolResult" && Array.isArray(msg.content)) {
275
+ for (const block of msg.content) {
276
+ if (block.type === "text" && typeof block.text === "string" && block.text.startsWith("[Output pruned")) {
277
+ count++;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return count;
283
+ }
284
+ function estimateTotalTokens(systemPrompt, messages, tools) {
285
+ let total = estimateTokens(systemPrompt);
286
+ total += estimateMessagesTokens(messages);
287
+ if (tools && tools.length > 0) {
288
+ // Rough estimate: stringify tool definitions
289
+ total += estimateTokens(JSON.stringify(tools));
290
+ }
291
+ return total;
292
+ }
293
+ //# sourceMappingURL=context-compactor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-compactor.js","sourceRoot":"","sources":["../src/context-compactor.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -51,4 +51,6 @@ export { assessTask, runCheck, runMetric, type AssessmentDeps, type CheckProgres
51
51
  export { DEFAULT_DIMENSIONS, buildRubricSection, computeWeightedScore, computeMedianScores } from "./assessment-scoring.js";
52
52
  export { validateReviewPayload, ReviewPayloadSchema, ReviewScoreSchema, REVIEW_JSON_SCHEMA, type ValidatedReviewPayload } from "./assessment-schemas.js";
53
53
  export { withRetry, isTransientError, type RetryOptions } from "./retry.js";
54
+ export { estimateTokens, estimateMessagesTokens, shouldCompact, pruneToolOutputs, compactIfNeeded, getCompactionPrompt, PRUNE_PROTECT, PRUNE_MINIMUM, TRIGGER_THRESHOLD, TARGET_AFTER, } from "./context-compactor.js";
55
+ export type { CompactionConfig, CompactionEvent, OnCompactionFn, SummarizeFn, CompactionInput, CompactionResult, } from "./context-compactor.js";
54
56
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -41,4 +41,6 @@ export { assessTask, runCheck, runMetric } from "./assessor.js";
41
41
  export { DEFAULT_DIMENSIONS, buildRubricSection, computeWeightedScore, computeMedianScores } from "./assessment-scoring.js";
42
42
  export { validateReviewPayload, ReviewPayloadSchema, ReviewScoreSchema, REVIEW_JSON_SCHEMA } from "./assessment-schemas.js";
43
43
  export { withRetry, isTransientError } from "./retry.js";
44
+ // ── Context Compaction ──────────────────────────────────────────────────
45
+ export { estimateTokens, estimateMessagesTokens, shouldCompact, pruneToolOutputs, compactIfNeeded, getCompactionPrompt, PRUNE_PROTECT, PRUNE_MINIMUM, TRIGGER_THRESHOLD, TARGET_AFTER, } from "./context-compactor.js";
44
46
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,cAAc,YAAY,CAAC;AAE3B,4EAA4E;AAC5E,cAAc,aAAa,CAAC;AAE5B,4EAA4E;AAC5E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEjG,4EAA4E;AAC5E,cAAc,cAAc,CAAC;AAE7B,4EAA4E;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgB1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAiBrD,2EAA2E;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,0EAA0E;AAC1E,OAAO,EACL,cAAc,EAAE,eAAe,EAAE,yBAAyB,EAC1D,gBAAgB,EAAE,qBAAqB,EAAE,gBAAgB,GAC1D,MAAM,oBAAoB,CAAC;AAG5B,0EAA0E;AAC1E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAYnE,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEzF,2EAA2E;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,2EAA2E;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,2EAA2E;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,2EAA2E;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAW9D,4EAA4E;AAC5E,OAAO,EAAE,sBAAsB,EAAwB,MAAM,8BAA8B,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,KAAK,EAAoE,MAAM,yBAAyB,CAAC;AAC5N,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAK/E,2EAA2E;AAC3E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAA8E,MAAM,eAAe,CAAC;AAC5I,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5H,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,kBAAkB,EAA+B,MAAM,yBAAyB,CAAC;AACzJ,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAqB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,cAAc,YAAY,CAAC;AAE3B,4EAA4E;AAC5E,cAAc,aAAa,CAAC;AAE5B,4EAA4E;AAC5E,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEjG,4EAA4E;AAC5E,cAAc,cAAc,CAAC;AAE7B,4EAA4E;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgB1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAiBrD,2EAA2E;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,0EAA0E;AAC1E,OAAO,EACL,cAAc,EAAE,eAAe,EAAE,yBAAyB,EAC1D,gBAAgB,EAAE,qBAAqB,EAAE,gBAAgB,GAC1D,MAAM,oBAAoB,CAAC;AAG5B,0EAA0E;AAC1E,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAYnE,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEzF,2EAA2E;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,2EAA2E;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,2EAA2E;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,2EAA2E;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAW9D,4EAA4E;AAC5E,OAAO,EAAE,sBAAsB,EAAwB,MAAM,8BAA8B,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,KAAK,EAAoE,MAAM,yBAAyB,CAAC;AAC5N,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAK/E,2EAA2E;AAC3E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAA8E,MAAM,eAAe,CAAC;AAC5I,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5H,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,kBAAkB,EAA+B,MAAM,yBAAyB,CAAC;AACzJ,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAqB,MAAM,YAAY,CAAC;AAE5E,2EAA2E;AAC3E,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polpo-ai/core",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Pure business logic, types, schemas, and store interfaces for the Polpo AI agent orchestration platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -193,6 +193,10 @@
193
193
  "./retry": {
194
194
  "types": "./dist/retry.d.ts",
195
195
  "import": "./dist/retry.js"
196
+ },
197
+ "./context-compactor": {
198
+ "types": "./dist/context-compactor.d.ts",
199
+ "import": "./dist/context-compactor.js"
196
200
  }
197
201
  },
198
202
  "sideEffects": false,