@animalabs/context-manager 0.2.0 → 0.4.0
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.
- package/dist/src/context-manager.d.ts +69 -2
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +128 -10
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/strategies/autobiographical.d.ts +217 -3
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +813 -77
- package/dist/src/strategies/autobiographical.js.map +1 -1
- package/dist/src/types/index.d.ts +2 -2
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/types/strategy.d.ts +208 -0
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js +21 -0
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/non-blocking-compile.test.d.ts +22 -0
- package/dist/test/non-blocking-compile.test.d.ts.map +1 -0
- package/dist/test/non-blocking-compile.test.js +114 -0
- package/dist/test/non-blocking-compile.test.js.map +1 -0
- package/dist/test/pins.test.d.ts +10 -0
- package/dist/test/pins.test.d.ts.map +1 -0
- package/dist/test/pins.test.js +164 -0
- package/dist/test/pins.test.js.map +1 -0
- package/dist/test/recall-positioning.test.d.ts +16 -0
- package/dist/test/recall-positioning.test.d.ts.map +1 -0
- package/dist/test/recall-positioning.test.js +181 -0
- package/dist/test/recall-positioning.test.js.map +1 -0
- package/dist/test/recent-window-eviction.test.d.ts +19 -0
- package/dist/test/recent-window-eviction.test.d.ts.map +1 -0
- package/dist/test/recent-window-eviction.test.js +206 -0
- package/dist/test/recent-window-eviction.test.js.map +1 -0
- package/dist/test/search.test.d.ts +10 -0
- package/dist/test/search.test.d.ts.map +1 -0
- package/dist/test/search.test.js +141 -0
- package/dist/test/search.test.js.map +1 -0
- package/dist/test/speculation-cap.test.d.ts +17 -0
- package/dist/test/speculation-cap.test.d.ts.map +1 -0
- package/dist/test/speculation-cap.test.js +157 -0
- package/dist/test/speculation-cap.test.js.map +1 -0
- package/dist/test/strategy-persistence.test.d.ts +15 -0
- package/dist/test/strategy-persistence.test.d.ts.map +1 -0
- package/dist/test/strategy-persistence.test.js +244 -0
- package/dist/test/strategy-persistence.test.js.map +1 -0
- package/dist/test/tool-pruning.test.d.ts +10 -0
- package/dist/test/tool-pruning.test.d.ts.map +1 -0
- package/dist/test/tool-pruning.test.js +140 -0
- package/dist/test/tool-pruning.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/context-manager.ts +142 -10
- package/src/strategies/autobiographical.ts +872 -81
- package/src/types/index.ts +14 -1
- package/src/types/strategy.ts +223 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AutobiographicalStrategy persistence + branch fork.
|
|
3
|
+
*
|
|
4
|
+
* Strategy state (summaries, summaryIdCounter, mergeQueue) was previously
|
|
5
|
+
* in-memory only — first process restart wiped the entire L1/L2/L3 pyramid.
|
|
6
|
+
* Forking the underlying chronicle branch silently shared state across
|
|
7
|
+
* branches because nothing was actually branch-scoped.
|
|
8
|
+
*
|
|
9
|
+
* These tests cover:
|
|
10
|
+
* 1. restart-and-resume: state survives close/reopen of the same path.
|
|
11
|
+
* 2. branch fork: a forked branch has its own summary timeline.
|
|
12
|
+
* 3. branch switch: switching back picks up the right branch's state.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
15
|
+
import assert from 'node:assert';
|
|
16
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
17
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
18
|
+
const TEST_STORE_PATH = './test-strategy-persistence';
|
|
19
|
+
function cleanup() {
|
|
20
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
21
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function textBlock(text) {
|
|
25
|
+
return [{ type: 'text', text }];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Test subclass that exposes the protected persistence helpers + lets the
|
|
29
|
+
* test seed summaries through the same code path that production uses.
|
|
30
|
+
*/
|
|
31
|
+
class TestableStrategy extends AutobiographicalStrategy {
|
|
32
|
+
seedL1(content, sourceIds) {
|
|
33
|
+
const entry = {
|
|
34
|
+
id: `L1-${this.nextSummaryIdCounter()}`,
|
|
35
|
+
level: 1,
|
|
36
|
+
content,
|
|
37
|
+
tokens: Math.ceil(content.length / 4),
|
|
38
|
+
sourceLevel: 0,
|
|
39
|
+
sourceIds,
|
|
40
|
+
sourceRange: {
|
|
41
|
+
first: sourceIds[0] ?? '',
|
|
42
|
+
last: sourceIds[sourceIds.length - 1] ?? '',
|
|
43
|
+
},
|
|
44
|
+
created: Date.now(),
|
|
45
|
+
};
|
|
46
|
+
this.pushSummary(entry);
|
|
47
|
+
return entry;
|
|
48
|
+
}
|
|
49
|
+
seedL2(sourceL1Ids, content = 'merged') {
|
|
50
|
+
const entry = {
|
|
51
|
+
id: `L2-${this.nextSummaryIdCounter()}`,
|
|
52
|
+
level: 2,
|
|
53
|
+
content,
|
|
54
|
+
tokens: Math.ceil(content.length / 4),
|
|
55
|
+
sourceLevel: 1,
|
|
56
|
+
sourceIds: sourceL1Ids,
|
|
57
|
+
sourceRange: { first: 'a', last: 'z' },
|
|
58
|
+
created: Date.now(),
|
|
59
|
+
};
|
|
60
|
+
this.pushSummary(entry);
|
|
61
|
+
for (const id of sourceL1Ids) {
|
|
62
|
+
const src = this.summaries.find(s => s.id === id);
|
|
63
|
+
if (src)
|
|
64
|
+
this.setMergedInto(src, entry.id);
|
|
65
|
+
}
|
|
66
|
+
return entry;
|
|
67
|
+
}
|
|
68
|
+
seedMerge(level, sourceIds) {
|
|
69
|
+
this.enqueueMerge({ level, sourceIds });
|
|
70
|
+
}
|
|
71
|
+
// Read-only views for assertions
|
|
72
|
+
getSummariesView() { return [...this.summaries]; }
|
|
73
|
+
getCounter() { return this.summaryIdCounter; }
|
|
74
|
+
getMergeQueueView() {
|
|
75
|
+
return [...this.mergeQueue];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
describe('AutobiographicalStrategy — persistence', () => {
|
|
79
|
+
before(cleanup);
|
|
80
|
+
after(cleanup);
|
|
81
|
+
beforeEach(cleanup);
|
|
82
|
+
it('restores summaries, counter, and merge queue across close/reopen', async () => {
|
|
83
|
+
// Phase 1: build state
|
|
84
|
+
{
|
|
85
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
86
|
+
const manager = await ContextManager.open({
|
|
87
|
+
path: TEST_STORE_PATH,
|
|
88
|
+
strategy,
|
|
89
|
+
});
|
|
90
|
+
manager.addMessage('User', textBlock('hello'));
|
|
91
|
+
const m2 = manager.addMessage('Claude', textBlock('hi back'));
|
|
92
|
+
strategy.seedL1('summary one', ['a', 'b']);
|
|
93
|
+
strategy.seedL1('summary two', ['c', 'd']);
|
|
94
|
+
strategy.seedMerge(2, ['L1-0', 'L1-1']);
|
|
95
|
+
assert.equal(strategy.getSummariesView().length, 2);
|
|
96
|
+
assert.equal(strategy.getCounter(), 2);
|
|
97
|
+
assert.equal(strategy.getMergeQueueView().length, 1);
|
|
98
|
+
manager.sync();
|
|
99
|
+
manager.close();
|
|
100
|
+
}
|
|
101
|
+
// Phase 2: reopen, verify reload
|
|
102
|
+
{
|
|
103
|
+
const strategy2 = new TestableStrategy({ targetChunkTokens: 300 });
|
|
104
|
+
const manager2 = await ContextManager.open({
|
|
105
|
+
path: TEST_STORE_PATH,
|
|
106
|
+
strategy: strategy2,
|
|
107
|
+
});
|
|
108
|
+
const summaries = strategy2.getSummariesView();
|
|
109
|
+
assert.equal(summaries.length, 2, 'summaries should reload from chronicle');
|
|
110
|
+
assert.equal(summaries[0].content, 'summary one');
|
|
111
|
+
assert.equal(summaries[1].content, 'summary two');
|
|
112
|
+
assert.equal(strategy2.getCounter(), 2, 'counter should reload');
|
|
113
|
+
const queue = strategy2.getMergeQueueView();
|
|
114
|
+
assert.equal(queue.length, 1, 'merge queue should reload');
|
|
115
|
+
assert.equal(queue[0].level, 2);
|
|
116
|
+
assert.deepEqual(queue[0].sourceIds, ['L1-0', 'L1-1']);
|
|
117
|
+
manager2.close();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
it('persists mergedInto edits so merged sources stay merged after restart', async () => {
|
|
121
|
+
{
|
|
122
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
123
|
+
const manager = await ContextManager.open({
|
|
124
|
+
path: TEST_STORE_PATH,
|
|
125
|
+
strategy,
|
|
126
|
+
});
|
|
127
|
+
const a = strategy.seedL1('a', ['m1']);
|
|
128
|
+
const b = strategy.seedL1('b', ['m2']);
|
|
129
|
+
const merged = strategy.seedL2([a.id, b.id], 'merged a+b');
|
|
130
|
+
const summaries = strategy.getSummariesView();
|
|
131
|
+
assert.ok(summaries[0].mergedInto === merged.id);
|
|
132
|
+
assert.ok(summaries[1].mergedInto === merged.id);
|
|
133
|
+
manager.sync();
|
|
134
|
+
manager.close();
|
|
135
|
+
}
|
|
136
|
+
{
|
|
137
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
138
|
+
const manager = await ContextManager.open({
|
|
139
|
+
path: TEST_STORE_PATH,
|
|
140
|
+
strategy,
|
|
141
|
+
});
|
|
142
|
+
const summaries = strategy.getSummariesView();
|
|
143
|
+
assert.equal(summaries.length, 3);
|
|
144
|
+
assert.equal(summaries[0].mergedInto, 'L2-2', 'L1 mergedInto must persist');
|
|
145
|
+
assert.equal(summaries[1].mergedInto, 'L2-2');
|
|
146
|
+
assert.equal(summaries[2].mergedInto, undefined);
|
|
147
|
+
manager.close();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
it('isolates summary state per chronicle branch (fork)', async () => {
|
|
151
|
+
const strategy = new TestableStrategy({ targetChunkTokens: 300 });
|
|
152
|
+
const manager = await ContextManager.open({
|
|
153
|
+
path: TEST_STORE_PATH,
|
|
154
|
+
strategy,
|
|
155
|
+
});
|
|
156
|
+
// Build pre-fork state, then fork at current head so the forked branch
|
|
157
|
+
// inherits everything we've written so far.
|
|
158
|
+
manager.addMessage('User', textBlock('m1'));
|
|
159
|
+
manager.addMessage('Claude', textBlock('m2'));
|
|
160
|
+
strategy.seedL1('common L1 (pre-fork)', ['m1', 'm2']);
|
|
161
|
+
const mainBranchName = manager.currentBranch().name;
|
|
162
|
+
const forkedName = await manager.fork('experimental');
|
|
163
|
+
assert.equal(forkedName, 'experimental');
|
|
164
|
+
assert.notEqual(forkedName, mainBranchName);
|
|
165
|
+
// On the forked branch, add a divergent summary
|
|
166
|
+
const strategyAfterFork = manager.getStrategy();
|
|
167
|
+
assert.equal(strategyAfterFork.getSummariesView().length, 1, 'forked branch should start with the pre-fork summary');
|
|
168
|
+
strategyAfterFork.seedL1('divergent on fork', ['x', 'y']);
|
|
169
|
+
assert.equal(strategyAfterFork.getSummariesView().length, 2);
|
|
170
|
+
// Switch back to main: should see only the pre-fork summary
|
|
171
|
+
await manager.switchBranch(mainBranchName);
|
|
172
|
+
const mainStrategy = manager.getStrategy();
|
|
173
|
+
const mainSummaries = mainStrategy.getSummariesView();
|
|
174
|
+
assert.equal(mainSummaries.length, 1, 'main branch must NOT see summaries created on fork');
|
|
175
|
+
assert.equal(mainSummaries[0].content, 'common L1 (pre-fork)');
|
|
176
|
+
// Add a summary unique to main
|
|
177
|
+
mainStrategy.seedL1('unique to main', ['p', 'q']);
|
|
178
|
+
assert.equal(mainStrategy.getSummariesView().length, 2);
|
|
179
|
+
// Switch back to fork: should still have its own divergent state
|
|
180
|
+
await manager.switchBranch(forkedName);
|
|
181
|
+
const forkStrategy = manager.getStrategy();
|
|
182
|
+
const forkSummaries = forkStrategy.getSummariesView();
|
|
183
|
+
assert.equal(forkSummaries.length, 2, 'fork branch should still have 2 summaries');
|
|
184
|
+
assert.equal(forkSummaries[1].content, 'divergent on fork');
|
|
185
|
+
assert.ok(!forkSummaries.some(s => s.content === 'unique to main'), 'fork must not see main-only summary');
|
|
186
|
+
manager.close();
|
|
187
|
+
});
|
|
188
|
+
it('keeps merge queue intact when executeMerge throws (no eager dequeue)', async () => {
|
|
189
|
+
// Regression test for the eager-dequeue footgun: dequeueMerge() used to
|
|
190
|
+
// run *before* the LLM call, so a transient failure (429, network drop,
|
|
191
|
+
// timeout) would silently drop the merge from the persisted queue
|
|
192
|
+
// forever. The fix is peek + shift-on-success — these assertions lock
|
|
193
|
+
// that contract in.
|
|
194
|
+
class FailingMergeStrategy extends TestableStrategy {
|
|
195
|
+
executeMergeError = new Error('simulated LLM failure');
|
|
196
|
+
async executeMerge(_level, _sourceIds, _ctx) {
|
|
197
|
+
throw this.executeMergeError;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const stubMembrane = {};
|
|
201
|
+
// Phase 1: enqueue a merge, force tick to fail, verify queue intact
|
|
202
|
+
{
|
|
203
|
+
const strategy = new FailingMergeStrategy({
|
|
204
|
+
targetChunkTokens: 300,
|
|
205
|
+
hierarchical: true,
|
|
206
|
+
});
|
|
207
|
+
const manager = await ContextManager.open({
|
|
208
|
+
path: TEST_STORE_PATH,
|
|
209
|
+
strategy,
|
|
210
|
+
membrane: stubMembrane,
|
|
211
|
+
});
|
|
212
|
+
strategy.seedMerge(2, ['L1-0', 'L1-1']);
|
|
213
|
+
assert.equal(strategy.getMergeQueueView().length, 1, 'merge enqueued');
|
|
214
|
+
// tick() will pick up the merge, invoke (our failing) executeMerge,
|
|
215
|
+
// and surface the error. The queue must still be intact afterward.
|
|
216
|
+
await assert.rejects(() => manager.tick(), /simulated LLM failure/, 'tick should propagate the LLM failure');
|
|
217
|
+
const queue = strategy.getMergeQueueView();
|
|
218
|
+
assert.equal(queue.length, 1, 'merge MUST remain in queue after failure');
|
|
219
|
+
assert.equal(queue[0].level, 2);
|
|
220
|
+
assert.deepEqual(queue[0].sourceIds, ['L1-0', 'L1-1']);
|
|
221
|
+
manager.sync();
|
|
222
|
+
manager.close();
|
|
223
|
+
}
|
|
224
|
+
// Phase 2: reopen the store; the un-merged entry must still be there.
|
|
225
|
+
// This is the part that mattered most pre-fix — the failure was *durable*
|
|
226
|
+
// because dequeueMerge persisted the shorter queue before the LLM call.
|
|
227
|
+
{
|
|
228
|
+
const strategy = new TestableStrategy({
|
|
229
|
+
targetChunkTokens: 300,
|
|
230
|
+
hierarchical: true,
|
|
231
|
+
});
|
|
232
|
+
const manager = await ContextManager.open({
|
|
233
|
+
path: TEST_STORE_PATH,
|
|
234
|
+
strategy,
|
|
235
|
+
});
|
|
236
|
+
const queue = strategy.getMergeQueueView();
|
|
237
|
+
assert.equal(queue.length, 1, 'merge MUST survive close/reopen');
|
|
238
|
+
assert.equal(queue[0].level, 2);
|
|
239
|
+
assert.deepEqual(queue[0].sourceIds, ['L1-0', 'L1-1']);
|
|
240
|
+
manager.close();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
//# sourceMappingURL=strategy-persistence.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-persistence.test.js","sourceRoot":"","sources":["../../test/strategy-persistence.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tool-result pruning + tool_use input truncation (audit gap #4).
|
|
3
|
+
*
|
|
4
|
+
* For tool-heavy sessions, dozens or hundreds of repeated tool calls can
|
|
5
|
+
* dominate context. This pruning pass keeps the most recent N results per
|
|
6
|
+
* tool name (older ones get a brief marker), and optionally truncates
|
|
7
|
+
* tool_use input blobs whose serialized JSON exceeds a token cap.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=tool-pruning.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-pruning.test.d.ts","sourceRoot":"","sources":["../../test/tool-pruning.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tool-result pruning + tool_use input truncation (audit gap #4).
|
|
3
|
+
*
|
|
4
|
+
* For tool-heavy sessions, dozens or hundreds of repeated tool calls can
|
|
5
|
+
* dominate context. This pruning pass keeps the most recent N results per
|
|
6
|
+
* tool name (older ones get a brief marker), and optionally truncates
|
|
7
|
+
* tool_use input blobs whose serialized JSON exceeds a token cap.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
10
|
+
import assert from 'node:assert';
|
|
11
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
12
|
+
import { ContextManager, AutobiographicalStrategy } from '../src/index.js';
|
|
13
|
+
const TEST_STORE_PATH = './test-tool-pruning';
|
|
14
|
+
function cleanup() {
|
|
15
|
+
if (existsSync(TEST_STORE_PATH)) {
|
|
16
|
+
rmSync(TEST_STORE_PATH, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function toolUse(id, name, input) {
|
|
20
|
+
return { type: 'tool_use', id, name, input };
|
|
21
|
+
}
|
|
22
|
+
function toolResult(id, content) {
|
|
23
|
+
return { type: 'tool_result', toolUseId: id, content };
|
|
24
|
+
}
|
|
25
|
+
describe('AutobiographicalStrategy — tool pruning (gap #4)', () => {
|
|
26
|
+
before(cleanup);
|
|
27
|
+
after(cleanup);
|
|
28
|
+
beforeEach(cleanup);
|
|
29
|
+
it('keeps last N tool_results per tool, replacing older ones with a marker', async () => {
|
|
30
|
+
const strategy = new AutobiographicalStrategy({
|
|
31
|
+
headWindowTokens: 0,
|
|
32
|
+
recentWindowTokens: 100_000, // keep all in recent for this test
|
|
33
|
+
toolResultMaxLastN: { 'fs:read': 2 }, // keep last 2 fs:read results
|
|
34
|
+
});
|
|
35
|
+
const manager = await ContextManager.open({
|
|
36
|
+
path: TEST_STORE_PATH,
|
|
37
|
+
strategy,
|
|
38
|
+
});
|
|
39
|
+
// Five tool_use+tool_result pairs for 'fs:read', plus one for 'http:get'.
|
|
40
|
+
for (let i = 0; i < 5; i++) {
|
|
41
|
+
const id = `tu-${i}`;
|
|
42
|
+
manager.addMessage('Claude', [toolUse(id, 'fs:read', { path: `/file${i}` })]);
|
|
43
|
+
manager.addMessage('user', [toolResult(id, `<file ${i} content here, pretend it is verbose>`)]);
|
|
44
|
+
}
|
|
45
|
+
manager.addMessage('Claude', [toolUse('tu-http', 'http:get', { url: 'x' })]);
|
|
46
|
+
manager.addMessage('user', [toolResult('tu-http', 'HTTP body')]);
|
|
47
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
48
|
+
const allBlocks = compiled.messages.flatMap(m => m.content);
|
|
49
|
+
const fsResults = allBlocks.filter((b) => b.type === 'tool_result' && b.toolUseId.startsWith('tu-') && b.toolUseId !== 'tu-http');
|
|
50
|
+
assert.equal(fsResults.length, 5, 'all 5 fs:read tool_result blocks should still be present (just rewritten)');
|
|
51
|
+
const truncated = fsResults.filter(r => typeof r.content === 'string' && r.content.includes('Result truncated'));
|
|
52
|
+
const intact = fsResults.filter(r => typeof r.content === 'string' && r.content.includes('file '));
|
|
53
|
+
assert.equal(truncated.length, 3, 'first 3 should be truncated (older)');
|
|
54
|
+
assert.equal(intact.length, 2, 'last 2 should survive intact');
|
|
55
|
+
// The last two intact ones must be tu-3 and tu-4 (most recent).
|
|
56
|
+
const intactIds = intact.map(r => r.toolUseId).sort();
|
|
57
|
+
assert.deepEqual(intactIds, ['tu-3', 'tu-4']);
|
|
58
|
+
// http:get result has no limit configured, so it stays intact.
|
|
59
|
+
const httpResult = allBlocks.find((b) => b.type === 'tool_result' && b.toolUseId === 'tu-http');
|
|
60
|
+
assert.ok(httpResult);
|
|
61
|
+
assert.equal(httpResult.content, 'HTTP body', 'http:get untouched (no limit)');
|
|
62
|
+
manager.close();
|
|
63
|
+
});
|
|
64
|
+
it('global numeric cap applies to all tools', async () => {
|
|
65
|
+
const strategy = new AutobiographicalStrategy({
|
|
66
|
+
headWindowTokens: 0,
|
|
67
|
+
recentWindowTokens: 100_000,
|
|
68
|
+
toolResultMaxLastN: 1, // keep only the most recent result per tool, period
|
|
69
|
+
});
|
|
70
|
+
const manager = await ContextManager.open({
|
|
71
|
+
path: TEST_STORE_PATH,
|
|
72
|
+
strategy,
|
|
73
|
+
});
|
|
74
|
+
for (let i = 0; i < 3; i++) {
|
|
75
|
+
manager.addMessage('Claude', [toolUse(`a-${i}`, 'tool-a', { i })]);
|
|
76
|
+
manager.addMessage('user', [toolResult(`a-${i}`, `result-a-${i}`)]);
|
|
77
|
+
manager.addMessage('Claude', [toolUse(`b-${i}`, 'tool-b', { i })]);
|
|
78
|
+
manager.addMessage('user', [toolResult(`b-${i}`, `result-b-${i}`)]);
|
|
79
|
+
}
|
|
80
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
81
|
+
const allBlocks = compiled.messages.flatMap(m => m.content);
|
|
82
|
+
const intactA = allBlocks.filter((b) => b.type === 'tool_result' && b.toolUseId.startsWith('a-') && typeof b.content === 'string' && b.content.startsWith('result-'));
|
|
83
|
+
const intactB = allBlocks.filter((b) => b.type === 'tool_result' && b.toolUseId.startsWith('b-') && typeof b.content === 'string' && b.content.startsWith('result-'));
|
|
84
|
+
assert.equal(intactA.length, 1, 'only 1 result-a survives (global cap=1)');
|
|
85
|
+
assert.equal(intactB.length, 1, 'only 1 result-b survives');
|
|
86
|
+
assert.equal(intactA[0].toolUseId, 'a-2', 'most recent a survives');
|
|
87
|
+
assert.equal(intactB[0].toolUseId, 'b-2', 'most recent b survives');
|
|
88
|
+
});
|
|
89
|
+
it('truncates oversized tool_use input when toolUseInputMaxTokens is set', async () => {
|
|
90
|
+
const strategy = new AutobiographicalStrategy({
|
|
91
|
+
headWindowTokens: 0,
|
|
92
|
+
recentWindowTokens: 100_000,
|
|
93
|
+
toolUseInputMaxTokens: 10, // ~40 chars
|
|
94
|
+
});
|
|
95
|
+
const manager = await ContextManager.open({
|
|
96
|
+
path: TEST_STORE_PATH,
|
|
97
|
+
strategy,
|
|
98
|
+
});
|
|
99
|
+
manager.addMessage('Claude', [
|
|
100
|
+
toolUse('x', 'fs:write', {
|
|
101
|
+
path: '/very-long-file-name.txt',
|
|
102
|
+
content: 'X'.repeat(2000), // ≈ 500 tokens — well over cap
|
|
103
|
+
encoding: 'utf-8',
|
|
104
|
+
}),
|
|
105
|
+
]);
|
|
106
|
+
manager.addMessage('user', [toolResult('x', 'ok')]);
|
|
107
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
108
|
+
const useBlock = compiled.messages
|
|
109
|
+
.flatMap(m => m.content)
|
|
110
|
+
.find((b) => b.type === 'tool_use');
|
|
111
|
+
assert.ok(useBlock);
|
|
112
|
+
assert.equal(useBlock.input._truncated, true, 'input must be flagged as truncated');
|
|
113
|
+
assert.ok(typeof useBlock.input._originalTokens === 'number');
|
|
114
|
+
assert.ok(Array.isArray(useBlock.input._keys));
|
|
115
|
+
// The original keys list should have been preserved as a hint
|
|
116
|
+
assert.ok(useBlock.input._keys.includes('path'));
|
|
117
|
+
assert.ok(useBlock.input._keys.includes('content'));
|
|
118
|
+
// The original `content` body must be GONE (this is the whole point)
|
|
119
|
+
assert.equal(useBlock.input.content, undefined);
|
|
120
|
+
});
|
|
121
|
+
it('does nothing when both pruning configs are unset (default)', async () => {
|
|
122
|
+
const strategy = new AutobiographicalStrategy({
|
|
123
|
+
headWindowTokens: 0,
|
|
124
|
+
recentWindowTokens: 100_000,
|
|
125
|
+
});
|
|
126
|
+
const manager = await ContextManager.open({
|
|
127
|
+
path: TEST_STORE_PATH,
|
|
128
|
+
strategy,
|
|
129
|
+
});
|
|
130
|
+
for (let i = 0; i < 4; i++) {
|
|
131
|
+
manager.addMessage('Claude', [toolUse(`t-${i}`, 'unused', { i })]);
|
|
132
|
+
manager.addMessage('user', [toolResult(`t-${i}`, `body-${i}`)]);
|
|
133
|
+
}
|
|
134
|
+
const compiled = await manager.compile({ maxTokens: 100_000, reserveForResponse: 0 });
|
|
135
|
+
const allBlocks = compiled.messages.flatMap(m => m.content);
|
|
136
|
+
const intact = allBlocks.filter(b => b.type === 'tool_result' && typeof b.content === 'string' && b.content.startsWith('body-'));
|
|
137
|
+
assert.equal(intact.length, 4, 'all results untouched when no pruning configured');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=tool-pruning.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-pruning.test.js","sourceRoot":"","sources":["../../test/tool-pruning.test.ts"],"names":[],"mappings":"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"}
|