squeezr-ai 1.81.2 → 1.90.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/bin/squeezr.js +82 -1
- package/dist/__tests__/bench.test.d.ts +1 -0
- package/dist/__tests__/bench.test.js +55 -0
- package/dist/__tests__/cacheAligner.test.d.ts +1 -0
- package/dist/__tests__/cacheAligner.test.js +75 -0
- package/dist/__tests__/codeStructure.test.d.ts +1 -0
- package/dist/__tests__/codeStructure.test.js +74 -0
- package/dist/__tests__/deterministic.test.js +13 -0
- package/dist/__tests__/doctor.test.d.ts +1 -0
- package/dist/__tests__/doctor.test.js +70 -0
- package/dist/__tests__/jsonCrush.test.d.ts +1 -0
- package/dist/__tests__/jsonCrush.test.js +110 -0
- package/dist/__tests__/learn.test.d.ts +1 -0
- package/dist/__tests__/learn.test.js +159 -0
- package/dist/__tests__/outputShaper.test.d.ts +1 -0
- package/dist/__tests__/outputShaper.test.js +216 -0
- package/dist/__tests__/textCrusher.test.d.ts +1 -0
- package/dist/__tests__/textCrusher.test.js +71 -0
- package/dist/bench.d.ts +45 -0
- package/dist/bench.js +114 -0
- package/dist/cacheAligner.d.ts +34 -0
- package/dist/cacheAligner.js +73 -0
- package/dist/compressor.js +11 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +25 -0
- package/dist/deterministic.d.ts +3 -0
- package/dist/deterministic.js +37 -5
- package/dist/doctor.d.ts +33 -0
- package/dist/doctor.js +102 -0
- package/dist/jsonCrush.d.ts +30 -0
- package/dist/jsonCrush.js +112 -0
- package/dist/learn.d.ts +65 -0
- package/dist/learn.js +244 -0
- package/dist/outputShaper.d.ts +71 -0
- package/dist/outputShaper.js +155 -0
- package/dist/server.js +22 -0
- package/dist/textCrusher.d.ts +31 -0
- package/dist/textCrusher.js +98 -0
- package/package.json +1 -1
- package/squeezr.toml +15 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { canonicalSignature, extractToolCalls, detectLoops, renderCorrections, writeMarkerBlock, LEARN_START, LEARN_END, } from '../learn.js';
|
|
3
|
+
// ── canonicalSignature ───────────────────────────────────────────────────────
|
|
4
|
+
describe('canonicalSignature', () => {
|
|
5
|
+
it('strips pipe-head/tail pagination so variants collapse', () => {
|
|
6
|
+
const a = canonicalSignature('Bash', { command: 'grep foo src | head -50' });
|
|
7
|
+
const b = canonicalSignature('Bash', { command: 'grep foo src | head -100' });
|
|
8
|
+
expect(a).toBe(b);
|
|
9
|
+
});
|
|
10
|
+
it('collapses LIMIT/OFFSET and bare integers to N', () => {
|
|
11
|
+
const a = canonicalSignature('Bash', { command: 'psql -c "select * from t LIMIT 20 OFFSET 0"' });
|
|
12
|
+
const b = canonicalSignature('Bash', { command: 'psql -c "select * from t LIMIT 50 OFFSET 40"' });
|
|
13
|
+
expect(a).toBe(b);
|
|
14
|
+
});
|
|
15
|
+
it('distinguishes genuinely different commands', () => {
|
|
16
|
+
const a = canonicalSignature('Bash', { command: 'ls -la' });
|
|
17
|
+
const b = canonicalSignature('Bash', { command: 'cat file.txt' });
|
|
18
|
+
expect(a).not.toBe(b);
|
|
19
|
+
});
|
|
20
|
+
it('uses tool name + input for non-Bash tools', () => {
|
|
21
|
+
const a = canonicalSignature('Grep', { pattern: 'foo', path: 'src' });
|
|
22
|
+
const b = canonicalSignature('Read', { file_path: 'src' });
|
|
23
|
+
expect(a).not.toBe(b);
|
|
24
|
+
expect(a.startsWith('grep')).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
// ── extractToolCalls ─────────────────────────────────────────────────────────
|
|
28
|
+
describe('extractToolCalls', () => {
|
|
29
|
+
const lines = [
|
|
30
|
+
JSON.stringify({ type: 'user', message: { content: [{ type: 'text', text: 'hi' }] } }),
|
|
31
|
+
JSON.stringify({ type: 'assistant', message: { content: [{ type: 'tool_use', id: 't1', name: 'Bash', input: { command: 'npm test' } }] } }),
|
|
32
|
+
JSON.stringify({ type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: 't1', content: 'FAIL', is_error: true }] } }),
|
|
33
|
+
];
|
|
34
|
+
it('pairs tool_use with its tool_result (name, error flag, output size)', () => {
|
|
35
|
+
const calls = extractToolCalls(lines);
|
|
36
|
+
expect(calls.length).toBe(1);
|
|
37
|
+
expect(calls[0].tool).toBe('Bash');
|
|
38
|
+
expect(calls[0].isError).toBe(true);
|
|
39
|
+
expect(calls[0].outputBytes).toBe('FAIL'.length);
|
|
40
|
+
});
|
|
41
|
+
it('tolerates malformed / non-JSON lines', () => {
|
|
42
|
+
const calls = extractToolCalls([...lines, 'not json', '']);
|
|
43
|
+
expect(calls.length).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
it('handles array-form tool_result content', () => {
|
|
46
|
+
const l = [
|
|
47
|
+
JSON.stringify({ type: 'assistant', message: { content: [{ type: 'tool_use', id: 'x', name: 'Read', input: { file_path: 'a' } }] } }),
|
|
48
|
+
JSON.stringify({ type: 'user', message: { content: [{ type: 'tool_result', tool_use_id: 'x', content: [{ type: 'text', text: 'hello' }] }] } }),
|
|
49
|
+
];
|
|
50
|
+
const calls = extractToolCalls(l);
|
|
51
|
+
expect(calls[0].outputBytes).toBeGreaterThan(0);
|
|
52
|
+
expect(calls[0].isError).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
// ── detectLoops ──────────────────────────────────────────────────────────────
|
|
56
|
+
function call(tool, command, isError, outputBytes = 100) {
|
|
57
|
+
return { tool, signature: canonicalSignature(tool, { command }), raw: command, isError, outputBytes };
|
|
58
|
+
}
|
|
59
|
+
describe('detectLoops', () => {
|
|
60
|
+
it('flags an error loop when the same signature fails >=3 times', () => {
|
|
61
|
+
const calls = [
|
|
62
|
+
call('Bash', 'npm run build', true, 500),
|
|
63
|
+
call('Bash', 'npm run build', true, 500),
|
|
64
|
+
call('Bash', 'npm run build', true, 500),
|
|
65
|
+
];
|
|
66
|
+
const loops = detectLoops(calls);
|
|
67
|
+
const err = loops.find(l => l.kind === 'error');
|
|
68
|
+
expect(err).toBeDefined();
|
|
69
|
+
expect(err.count).toBe(3);
|
|
70
|
+
expect(err.wastedBytes).toBe(1500);
|
|
71
|
+
});
|
|
72
|
+
it('does not flag fewer than the minimum occurrences', () => {
|
|
73
|
+
const calls = [call('Bash', 'npm run build', true), call('Bash', 'npm run build', true)];
|
|
74
|
+
expect(detectLoops(calls).length).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
it('flags a refetch loop: same signature, different raw variants, all succeeding', () => {
|
|
77
|
+
const calls = [
|
|
78
|
+
call('Bash', 'grep foo src | head -50', false, 2000),
|
|
79
|
+
call('Bash', 'grep foo src | head -100', false, 4000),
|
|
80
|
+
call('Bash', 'grep foo src | head -200', false, 8000),
|
|
81
|
+
];
|
|
82
|
+
const loops = detectLoops(calls);
|
|
83
|
+
const refetch = loops.find(l => l.kind === 'refetch');
|
|
84
|
+
expect(refetch).toBeDefined();
|
|
85
|
+
expect(refetch.count).toBe(3);
|
|
86
|
+
// wasted = redundant follow-ups (all but the first)
|
|
87
|
+
expect(refetch.wastedBytes).toBe(12000);
|
|
88
|
+
});
|
|
89
|
+
it('does not flag a refetch when the raw command is identical every time (that is caching, not a loop)', () => {
|
|
90
|
+
const calls = [
|
|
91
|
+
call('Bash', 'ls', false),
|
|
92
|
+
call('Bash', 'ls', false),
|
|
93
|
+
call('Bash', 'ls', false),
|
|
94
|
+
];
|
|
95
|
+
expect(detectLoops(calls).find(l => l.kind === 'refetch')).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
it('classifies a signature with errors as an error loop, not a refetch (even with raw variants)', () => {
|
|
98
|
+
// Same canonical signature (pagination stripped) + distinct raw + all errored:
|
|
99
|
+
// errors win → error loop, never also reported as a refetch.
|
|
100
|
+
const calls = [
|
|
101
|
+
call('Bash', 'grep foo | head -50', true),
|
|
102
|
+
call('Bash', 'grep foo | head -100', true),
|
|
103
|
+
call('Bash', 'grep foo | head -200', true),
|
|
104
|
+
];
|
|
105
|
+
const loops = detectLoops(calls);
|
|
106
|
+
expect(loops.some(l => l.kind === 'error')).toBe(true);
|
|
107
|
+
expect(loops.some(l => l.kind === 'refetch')).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
it('ranks loops by wasted bytes descending', () => {
|
|
110
|
+
const calls = [
|
|
111
|
+
call('Bash', 'small | head -1', false, 100),
|
|
112
|
+
call('Bash', 'small | head -2', false, 100),
|
|
113
|
+
call('Bash', 'small | head -3', false, 100),
|
|
114
|
+
call('Bash', 'big', true, 9000),
|
|
115
|
+
call('Bash', 'big', true, 9000),
|
|
116
|
+
call('Bash', 'big', true, 9000),
|
|
117
|
+
];
|
|
118
|
+
const loops = detectLoops(calls);
|
|
119
|
+
expect(loops[0].wastedBytes).toBeGreaterThanOrEqual(loops[loops.length - 1].wastedBytes);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ── renderCorrections ────────────────────────────────────────────────────────
|
|
123
|
+
describe('renderCorrections', () => {
|
|
124
|
+
it('produces a bullet per loop mentioning the signature', () => {
|
|
125
|
+
const loops = detectLoops([
|
|
126
|
+
call('Bash', 'npm run build', true, 500),
|
|
127
|
+
call('Bash', 'npm run build', true, 500),
|
|
128
|
+
call('Bash', 'npm run build', true, 500),
|
|
129
|
+
]);
|
|
130
|
+
const text = renderCorrections(loops);
|
|
131
|
+
expect(text.includes('npm run build')).toBe(true);
|
|
132
|
+
expect(text.split('\n').some(l => l.trim().startsWith('-'))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
it('returns empty string for no loops', () => {
|
|
135
|
+
expect(renderCorrections([])).toBe('');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
// ── writeMarkerBlock ─────────────────────────────────────────────────────────
|
|
139
|
+
describe('writeMarkerBlock', () => {
|
|
140
|
+
it('appends a marked block to fresh content', () => {
|
|
141
|
+
const out = writeMarkerBlock('# Project\n\nsome notes\n', 'rule one');
|
|
142
|
+
expect(out.includes(LEARN_START)).toBe(true);
|
|
143
|
+
expect(out.includes(LEARN_END)).toBe(true);
|
|
144
|
+
expect(out.includes('rule one')).toBe(true);
|
|
145
|
+
expect(out.startsWith('# Project')).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
it('replaces an existing block instead of stacking (idempotent shape)', () => {
|
|
148
|
+
const first = writeMarkerBlock('base\n', 'rule one');
|
|
149
|
+
const second = writeMarkerBlock(first, 'rule two');
|
|
150
|
+
expect((second.match(new RegExp(LEARN_START, 'g')) ?? []).length).toBe(1);
|
|
151
|
+
expect(second.includes('rule two')).toBe(true);
|
|
152
|
+
expect(second.includes('rule one')).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
it('re-applying the SAME block yields byte-identical content', () => {
|
|
155
|
+
const first = writeMarkerBlock('base\n', 'rule one');
|
|
156
|
+
const second = writeMarkerBlock(first, 'rule one');
|
|
157
|
+
expect(second).toBe(first);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { classifyTurn, steeringText, applyVerbositySteering, routeEffort, shapeRequest, STEERING_START, STEERING_END, } from '../outputShaper.js';
|
|
3
|
+
const DEFAULTS = {
|
|
4
|
+
enabled: true,
|
|
5
|
+
verbositySteering: true,
|
|
6
|
+
level: 2,
|
|
7
|
+
effortRouting: true,
|
|
8
|
+
mechanicalThinkingFloor: 1024,
|
|
9
|
+
};
|
|
10
|
+
function userText(t) {
|
|
11
|
+
return { role: 'user', content: [{ type: 'text', text: t }] };
|
|
12
|
+
}
|
|
13
|
+
function toolResult(id, content = 'ok', isError = false) {
|
|
14
|
+
return {
|
|
15
|
+
role: 'user',
|
|
16
|
+
content: [{ type: 'tool_result', tool_use_id: id, content, is_error: isError }],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function assistantToolUse(id) {
|
|
20
|
+
return { role: 'assistant', content: [{ type: 'tool_use', id, name: 'Bash', input: {} }] };
|
|
21
|
+
}
|
|
22
|
+
// ── classifyTurn ─────────────────────────────────────────────────────────────
|
|
23
|
+
describe('classifyTurn', () => {
|
|
24
|
+
it('a clean tool_result as the last message → mechanical', () => {
|
|
25
|
+
const msgs = [userText('do a thing'), assistantToolUse('t1'), toolResult('t1', 'file contents')];
|
|
26
|
+
expect(classifyTurn(msgs)).toBe('mechanical');
|
|
27
|
+
});
|
|
28
|
+
it('a tool_result with is_error → error', () => {
|
|
29
|
+
const msgs = [userText('do a thing'), assistantToolUse('t1'), toolResult('t1', 'boom', true)];
|
|
30
|
+
expect(classifyTurn(msgs)).toBe('error');
|
|
31
|
+
});
|
|
32
|
+
it('a fresh user text message → new-ask', () => {
|
|
33
|
+
const msgs = [userText('first'), assistantToolUse('t1'), toolResult('t1'), userText('now do this')];
|
|
34
|
+
expect(classifyTurn(msgs)).toBe('new-ask');
|
|
35
|
+
});
|
|
36
|
+
it('string user content → new-ask', () => {
|
|
37
|
+
const msgs = [{ role: 'user', content: 'hello there' }];
|
|
38
|
+
expect(classifyTurn(msgs)).toBe('new-ask');
|
|
39
|
+
});
|
|
40
|
+
it('tool_result string content field on error flag detected → error', () => {
|
|
41
|
+
const msgs = [
|
|
42
|
+
{ role: 'user', content: [{ type: 'tool_result', tool_use_id: 'x', content: 'err', is_error: true }] },
|
|
43
|
+
];
|
|
44
|
+
expect(classifyTurn(msgs)).toBe('error');
|
|
45
|
+
});
|
|
46
|
+
it('empty messages → unknown', () => {
|
|
47
|
+
expect(classifyTurn([])).toBe('unknown');
|
|
48
|
+
});
|
|
49
|
+
it('last message is assistant → unknown', () => {
|
|
50
|
+
const msgs = [userText('hi'), assistantToolUse('t1')];
|
|
51
|
+
expect(classifyTurn(msgs)).toBe('unknown');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
// ── steeringText ─────────────────────────────────────────────────────────────
|
|
55
|
+
describe('steeringText', () => {
|
|
56
|
+
it('is byte-stable for the same level', () => {
|
|
57
|
+
expect(steeringText(2)).toBe(steeringText(2));
|
|
58
|
+
});
|
|
59
|
+
it('higher levels are more aggressive (longer or different)', () => {
|
|
60
|
+
expect(steeringText(1)).not.toBe(steeringText(4));
|
|
61
|
+
});
|
|
62
|
+
it('is wrapped in the sentinel markers', () => {
|
|
63
|
+
const t = steeringText(2);
|
|
64
|
+
expect(t.startsWith(STEERING_START)).toBe(true);
|
|
65
|
+
expect(t.trimEnd().endsWith(STEERING_END)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
// ── applyVerbositySteering ───────────────────────────────────────────────────
|
|
69
|
+
describe('applyVerbositySteering', () => {
|
|
70
|
+
it('appends a text block to an array system prompt', () => {
|
|
71
|
+
const body = {
|
|
72
|
+
system: [{ type: 'text', text: 'You are Claude.', cache_control: { type: 'ephemeral' } }],
|
|
73
|
+
};
|
|
74
|
+
const changed = applyVerbositySteering(body, 2);
|
|
75
|
+
expect(changed).toBe(true);
|
|
76
|
+
const sys = body.system;
|
|
77
|
+
expect(sys.length).toBe(2);
|
|
78
|
+
expect(sys[1].text.includes(STEERING_START)).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
it('never mutates the existing cache_control block', () => {
|
|
81
|
+
const body = {
|
|
82
|
+
system: [{ type: 'text', text: 'You are Claude.', cache_control: { type: 'ephemeral' } }],
|
|
83
|
+
};
|
|
84
|
+
applyVerbositySteering(body, 2);
|
|
85
|
+
const sys = body.system;
|
|
86
|
+
expect(sys[0].text).toBe('You are Claude.');
|
|
87
|
+
expect(sys[0].cache_control).toEqual({ type: 'ephemeral' });
|
|
88
|
+
});
|
|
89
|
+
it('appends to a string system prompt', () => {
|
|
90
|
+
const body = { system: 'You are Claude.' };
|
|
91
|
+
applyVerbositySteering(body, 2);
|
|
92
|
+
expect(body.system.startsWith('You are Claude.')).toBe(true);
|
|
93
|
+
expect(body.system.includes(STEERING_START)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it('is idempotent — applying twice yields byte-identical system (cache-safe)', () => {
|
|
96
|
+
const body = {
|
|
97
|
+
system: [{ type: 'text', text: 'You are Claude.', cache_control: { type: 'ephemeral' } }],
|
|
98
|
+
};
|
|
99
|
+
applyVerbositySteering(body, 2);
|
|
100
|
+
const afterFirst = JSON.stringify(body.system);
|
|
101
|
+
applyVerbositySteering(body, 2);
|
|
102
|
+
const afterSecond = JSON.stringify(body.system);
|
|
103
|
+
expect(afterSecond).toBe(afterFirst);
|
|
104
|
+
});
|
|
105
|
+
it('re-steering at a new level replaces the old block (no stacking)', () => {
|
|
106
|
+
const body = {
|
|
107
|
+
system: [{ type: 'text', text: 'You are Claude.' }],
|
|
108
|
+
};
|
|
109
|
+
applyVerbositySteering(body, 2);
|
|
110
|
+
applyVerbositySteering(body, 4);
|
|
111
|
+
const sys = body.system;
|
|
112
|
+
const steeringBlocks = sys.filter(b => (b.text ?? '').includes(STEERING_START));
|
|
113
|
+
expect(steeringBlocks.length).toBe(1);
|
|
114
|
+
expect(steeringBlocks[0].text).toBe(steeringText(4));
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
// ── routeEffort ──────────────────────────────────────────────────────────────
|
|
118
|
+
describe('routeEffort', () => {
|
|
119
|
+
it('lowers an existing thinking.budget_tokens to the floor', () => {
|
|
120
|
+
const body = { thinking: { type: 'enabled', budget_tokens: 16000 } };
|
|
121
|
+
const lowered = routeEffort(body, 1024);
|
|
122
|
+
expect(lowered).toBe(true);
|
|
123
|
+
expect(body.thinking.budget_tokens).toBe(1024);
|
|
124
|
+
});
|
|
125
|
+
it('never toggles thinking.type', () => {
|
|
126
|
+
const body = { thinking: { type: 'enabled', budget_tokens: 16000 } };
|
|
127
|
+
routeEffort(body, 1024);
|
|
128
|
+
expect(body.thinking.type).toBe('enabled');
|
|
129
|
+
});
|
|
130
|
+
it('does not raise a budget already below the floor', () => {
|
|
131
|
+
const body = { thinking: { type: 'enabled', budget_tokens: 512 } };
|
|
132
|
+
const lowered = routeEffort(body, 1024);
|
|
133
|
+
expect(lowered).toBe(false);
|
|
134
|
+
expect(body.thinking.budget_tokens).toBe(512);
|
|
135
|
+
});
|
|
136
|
+
it('lowers an explicit output_config.effort but never injects one', () => {
|
|
137
|
+
const withEffort = { output_config: { effort: 'xhigh' } };
|
|
138
|
+
expect(routeEffort(withEffort, 1024)).toBe(true);
|
|
139
|
+
expect(withEffort.output_config.effort).toBe('low');
|
|
140
|
+
const withoutEffort = {};
|
|
141
|
+
expect(routeEffort(withoutEffort, 1024)).toBe(false);
|
|
142
|
+
expect(withoutEffort.output_config).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
it('does nothing when no effort levers are present', () => {
|
|
145
|
+
const body = {};
|
|
146
|
+
expect(routeEffort(body, 1024)).toBe(false);
|
|
147
|
+
expect(body.thinking).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
// ── shapeRequest (orchestration) ─────────────────────────────────────────────
|
|
151
|
+
describe('shapeRequest', () => {
|
|
152
|
+
it('no-op when disabled — body untouched', () => {
|
|
153
|
+
const body = {
|
|
154
|
+
system: [{ type: 'text', text: 'You are Claude.' }],
|
|
155
|
+
thinking: { type: 'enabled', budget_tokens: 16000 },
|
|
156
|
+
messages: [toolResult('t1')],
|
|
157
|
+
};
|
|
158
|
+
const before = JSON.stringify(body);
|
|
159
|
+
const r = shapeRequest(body, { ...DEFAULTS, enabled: false });
|
|
160
|
+
expect(r.steered).toBe(false);
|
|
161
|
+
expect(r.effortLowered).toBe(false);
|
|
162
|
+
expect(JSON.stringify(body)).toBe(before);
|
|
163
|
+
});
|
|
164
|
+
it('mechanical turn: both steers and lowers effort', () => {
|
|
165
|
+
const body = {
|
|
166
|
+
system: [{ type: 'text', text: 'You are Claude.' }],
|
|
167
|
+
thinking: { type: 'enabled', budget_tokens: 16000 },
|
|
168
|
+
messages: [userText('go'), assistantToolUse('t1'), toolResult('t1', 'contents')],
|
|
169
|
+
};
|
|
170
|
+
const r = shapeRequest(body, DEFAULTS);
|
|
171
|
+
expect(r.turn).toBe('mechanical');
|
|
172
|
+
expect(r.steered).toBe(true);
|
|
173
|
+
expect(r.effortLowered).toBe(true);
|
|
174
|
+
expect(body.thinking.budget_tokens).toBe(1024);
|
|
175
|
+
});
|
|
176
|
+
it('new-ask turn: steers but keeps full effort', () => {
|
|
177
|
+
const body = {
|
|
178
|
+
system: [{ type: 'text', text: 'You are Claude.' }],
|
|
179
|
+
thinking: { type: 'enabled', budget_tokens: 16000 },
|
|
180
|
+
messages: [toolResult('t1'), userText('new question')],
|
|
181
|
+
};
|
|
182
|
+
const r = shapeRequest(body, DEFAULTS);
|
|
183
|
+
expect(r.turn).toBe('new-ask');
|
|
184
|
+
expect(r.steered).toBe(true);
|
|
185
|
+
expect(r.effortLowered).toBe(false);
|
|
186
|
+
expect(body.thinking.budget_tokens).toBe(16000);
|
|
187
|
+
});
|
|
188
|
+
it('error turn: keeps full effort', () => {
|
|
189
|
+
const body = {
|
|
190
|
+
system: [{ type: 'text', text: 'You are Claude.' }],
|
|
191
|
+
thinking: { type: 'enabled', budget_tokens: 16000 },
|
|
192
|
+
messages: [assistantToolUse('t1'), toolResult('t1', 'boom', true)],
|
|
193
|
+
};
|
|
194
|
+
const r = shapeRequest(body, DEFAULTS);
|
|
195
|
+
expect(r.turn).toBe('error');
|
|
196
|
+
expect(r.effortLowered).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
it('effort routing disabled: never lowers even on mechanical turns', () => {
|
|
199
|
+
const body = {
|
|
200
|
+
thinking: { type: 'enabled', budget_tokens: 16000 },
|
|
201
|
+
messages: [toolResult('t1')],
|
|
202
|
+
};
|
|
203
|
+
const r = shapeRequest(body, { ...DEFAULTS, effortRouting: false });
|
|
204
|
+
expect(r.effortLowered).toBe(false);
|
|
205
|
+
expect(body.thinking.budget_tokens).toBe(16000);
|
|
206
|
+
});
|
|
207
|
+
it('verbosity steering disabled: never touches the system prompt', () => {
|
|
208
|
+
const body = {
|
|
209
|
+
system: [{ type: 'text', text: 'You are Claude.' }],
|
|
210
|
+
messages: [toolResult('t1')],
|
|
211
|
+
};
|
|
212
|
+
const r = shapeRequest(body, { ...DEFAULTS, verbositySteering: false });
|
|
213
|
+
expect(r.steered).toBe(false);
|
|
214
|
+
expect(body.system.length).toBe(1);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { crushText, normalizeLineKey } from '../textCrusher.js';
|
|
3
|
+
describe('normalizeLineKey', () => {
|
|
4
|
+
it('collapses numbers, hex and timestamps so near-duplicates share a key', () => {
|
|
5
|
+
const a = normalizeLineKey('2026-07-21T10:00:01Z downloaded chunk 12 (0x1a2b)');
|
|
6
|
+
const b = normalizeLineKey('2026-07-21T11:22:33Z downloaded chunk 998 (0xff00)');
|
|
7
|
+
expect(a).toBe(b);
|
|
8
|
+
});
|
|
9
|
+
it('keeps genuinely different lines distinct', () => {
|
|
10
|
+
expect(normalizeLineKey('connecting to db')).not.toBe(normalizeLineKey('closing db handle'));
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('crushText', () => {
|
|
14
|
+
function repetitiveLog(n) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
for (let i = 0; i < n; i++)
|
|
17
|
+
lines.push(`[worker] processed record ${i} in ${i * 2}ms`);
|
|
18
|
+
return lines.join('\n');
|
|
19
|
+
}
|
|
20
|
+
it('returns short input unchanged', () => {
|
|
21
|
+
const input = 'line a\nline b\nline c';
|
|
22
|
+
expect(crushText(input, { maxLines: 60 }).text).toBe(input);
|
|
23
|
+
});
|
|
24
|
+
it('collapses near-duplicate lines that differ only in numbers', () => {
|
|
25
|
+
const input = repetitiveLog(200);
|
|
26
|
+
const res = crushText(input);
|
|
27
|
+
expect(res.dropped).toBeGreaterThan(0);
|
|
28
|
+
expect(res.text.length).toBeLessThan(input.length);
|
|
29
|
+
expect(res.text.split('\n').length).toBeLessThan(200);
|
|
30
|
+
});
|
|
31
|
+
it('always keeps lines carrying error/failure signal', () => {
|
|
32
|
+
const lines = [];
|
|
33
|
+
for (let i = 0; i < 150; i++)
|
|
34
|
+
lines.push(`[worker] processed record ${i} ok`);
|
|
35
|
+
lines.splice(75, 0, 'ERROR: database connection refused at db.example.com:5432');
|
|
36
|
+
const input = lines.join('\n');
|
|
37
|
+
const res = crushText(input);
|
|
38
|
+
expect(res.text.includes('ERROR: database connection refused at db.example.com:5432')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it('keeps head and tail anchor lines', () => {
|
|
41
|
+
const lines = ['FIRST LINE MARKER'];
|
|
42
|
+
for (let i = 0; i < 150; i++)
|
|
43
|
+
lines.push(`noise line ${i} value ${i}`);
|
|
44
|
+
lines.push('LAST LINE MARKER');
|
|
45
|
+
const input = lines.join('\n');
|
|
46
|
+
const res = crushText(input, { headKeep: 3, tailKeep: 3 });
|
|
47
|
+
expect(res.text.includes('FIRST LINE MARKER')).toBe(true);
|
|
48
|
+
expect(res.text.includes('LAST LINE MARKER')).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('respects the maxLines budget (plus the omitted-summary line)', () => {
|
|
51
|
+
const res = crushText(repetitiveLog(500), { maxLines: 40 });
|
|
52
|
+
// kept content lines should not wildly exceed the budget
|
|
53
|
+
expect(res.text.split('\n').length).toBeLessThanOrEqual(45);
|
|
54
|
+
});
|
|
55
|
+
it('preserves the original order of kept lines', () => {
|
|
56
|
+
const lines = ['alpha unique', ...Array.from({ length: 100 }, (_, i) => `dup ${i}`), 'omega unique'];
|
|
57
|
+
const res = crushText(lines.join('\n'), { headKeep: 2, tailKeep: 2 });
|
|
58
|
+
const idxA = res.text.indexOf('alpha unique');
|
|
59
|
+
const idxO = res.text.indexOf('omega unique');
|
|
60
|
+
expect(idxA).toBeGreaterThanOrEqual(0);
|
|
61
|
+
expect(idxO).toBeGreaterThan(idxA);
|
|
62
|
+
});
|
|
63
|
+
it('is deterministic', () => {
|
|
64
|
+
const input = repetitiveLog(300);
|
|
65
|
+
expect(crushText(input).text).toBe(crushText(input).text);
|
|
66
|
+
});
|
|
67
|
+
it('emits an omitted-count marker when it drops lines', () => {
|
|
68
|
+
const res = crushText(repetitiveLog(300));
|
|
69
|
+
expect(/\[\d+ lines omitted/.test(res.text)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
package/dist/bench.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* squeezr bench — honest, reproducible compression+accuracy harness.
|
|
3
|
+
*
|
|
4
|
+
* Headroom's headline claim is "same answers, fewer tokens", proven by having an LLM
|
|
5
|
+
* answer questions over compressed data. That needs an API key and costs money, so it
|
|
6
|
+
* can't live in CI. This harness measures the same thing with a zero-cost, deterministic
|
|
7
|
+
* proxy for accuracy: FACT RECALL.
|
|
8
|
+
*
|
|
9
|
+
* For each fixture we declare the CRITICAL FACTS an answer would need (an error code, a
|
|
10
|
+
* filename, a specific value, a count). We compress the fixture through Squeezr's real
|
|
11
|
+
* deterministic pipeline and measure:
|
|
12
|
+
*
|
|
13
|
+
* - compressionPct — chars removed / original chars.
|
|
14
|
+
* - inlineRecall — fraction of critical facts still present VERBATIM in the compressed
|
|
15
|
+
* output (i.e. answerable WITHOUT an expand round-trip).
|
|
16
|
+
*
|
|
17
|
+
* Recoverability is 100% by design (every compressed block stores its original in the
|
|
18
|
+
* expand store — see expand.test.ts), so the meaningful, honest metric is how much
|
|
19
|
+
* survives inline. A fixture that trades recall for ratio (huge-log head/tail) shows it
|
|
20
|
+
* here rather than hiding it.
|
|
21
|
+
*/
|
|
22
|
+
export interface Fixture {
|
|
23
|
+
name: string;
|
|
24
|
+
tool: string;
|
|
25
|
+
content: string;
|
|
26
|
+
facts: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface BenchRow {
|
|
29
|
+
name: string;
|
|
30
|
+
originalChars: number;
|
|
31
|
+
compressedChars: number;
|
|
32
|
+
compressionPct: number;
|
|
33
|
+
inlineRecall: number;
|
|
34
|
+
}
|
|
35
|
+
export interface BenchSummary {
|
|
36
|
+
rows: BenchRow[];
|
|
37
|
+
avgCompressionPct: number;
|
|
38
|
+
avgInlineRecall: number;
|
|
39
|
+
}
|
|
40
|
+
/** Fraction of `facts` present verbatim in `text`. Empty fact list → 1 (nothing to lose). */
|
|
41
|
+
export declare function factRecall(text: string, facts: string[]): number;
|
|
42
|
+
export declare const FIXTURES: Fixture[];
|
|
43
|
+
export declare function runBench(fixtures?: Fixture[]): BenchSummary;
|
|
44
|
+
/** Human-readable table for the CLI. */
|
|
45
|
+
export declare function formatBench(summary: BenchSummary): string;
|
package/dist/bench.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* squeezr bench — honest, reproducible compression+accuracy harness.
|
|
3
|
+
*
|
|
4
|
+
* Headroom's headline claim is "same answers, fewer tokens", proven by having an LLM
|
|
5
|
+
* answer questions over compressed data. That needs an API key and costs money, so it
|
|
6
|
+
* can't live in CI. This harness measures the same thing with a zero-cost, deterministic
|
|
7
|
+
* proxy for accuracy: FACT RECALL.
|
|
8
|
+
*
|
|
9
|
+
* For each fixture we declare the CRITICAL FACTS an answer would need (an error code, a
|
|
10
|
+
* filename, a specific value, a count). We compress the fixture through Squeezr's real
|
|
11
|
+
* deterministic pipeline and measure:
|
|
12
|
+
*
|
|
13
|
+
* - compressionPct — chars removed / original chars.
|
|
14
|
+
* - inlineRecall — fraction of critical facts still present VERBATIM in the compressed
|
|
15
|
+
* output (i.e. answerable WITHOUT an expand round-trip).
|
|
16
|
+
*
|
|
17
|
+
* Recoverability is 100% by design (every compressed block stores its original in the
|
|
18
|
+
* expand store — see expand.test.ts), so the meaningful, honest metric is how much
|
|
19
|
+
* survives inline. A fixture that trades recall for ratio (huge-log head/tail) shows it
|
|
20
|
+
* here rather than hiding it.
|
|
21
|
+
*/
|
|
22
|
+
import { preprocessForTool } from './deterministic.js';
|
|
23
|
+
/** Fraction of `facts` present verbatim in `text`. Empty fact list → 1 (nothing to lose). */
|
|
24
|
+
export function factRecall(text, facts) {
|
|
25
|
+
if (facts.length === 0)
|
|
26
|
+
return 1;
|
|
27
|
+
const present = facts.filter(f => text.includes(f)).length;
|
|
28
|
+
return present / facts.length;
|
|
29
|
+
}
|
|
30
|
+
// ── Fixtures ─────────────────────────────────────────────────────────────────
|
|
31
|
+
function jsonArrayFixture() {
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (let i = 0; i < 40; i++) {
|
|
34
|
+
rows.push({ id: i, service: `svc-${i}`, status: i === 17 ? 'CrashLoopBackOff' : 'Running', restarts: i === 17 ? 42 : 0, node: 'ip-10-0-1-5' });
|
|
35
|
+
}
|
|
36
|
+
return JSON.stringify(rows);
|
|
37
|
+
}
|
|
38
|
+
function buildLogFixture() {
|
|
39
|
+
const lines = [];
|
|
40
|
+
for (let i = 0; i < 60; i++)
|
|
41
|
+
lines.push('[webpack] compiling module... ok');
|
|
42
|
+
lines.push('ERROR in ./src/app.ts:128:14');
|
|
43
|
+
lines.push("TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.");
|
|
44
|
+
for (let i = 0; i < 60; i++)
|
|
45
|
+
lines.push('[webpack] compiling module... ok');
|
|
46
|
+
return lines.join('\n');
|
|
47
|
+
}
|
|
48
|
+
function grepFixture() {
|
|
49
|
+
const lines = [];
|
|
50
|
+
for (let f = 0; f < 30; f++) {
|
|
51
|
+
for (let l = 0; l < 5; l++)
|
|
52
|
+
lines.push(`src/mod${f}.ts:${l + 1}: const value = computeThing(${l})`);
|
|
53
|
+
}
|
|
54
|
+
lines.push('src/critical.ts:99: throw new FatalError("DISK_FULL")');
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
export const FIXTURES = [
|
|
58
|
+
{
|
|
59
|
+
name: 'json-array (k8s pods)',
|
|
60
|
+
tool: 'Bash',
|
|
61
|
+
content: jsonArrayFixture(),
|
|
62
|
+
// The one anomalous row an answer would need + its schema.
|
|
63
|
+
facts: ['CrashLoopBackOff', '42', 'svc-17', 'ip-10-0-1-5'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'build-log (tsc error in noise)',
|
|
67
|
+
tool: 'Bash',
|
|
68
|
+
content: buildLogFixture(),
|
|
69
|
+
facts: ['TS2345', './src/app.ts:128:14'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'grep-dump (fatal among matches)',
|
|
73
|
+
tool: 'Grep',
|
|
74
|
+
content: grepFixture(),
|
|
75
|
+
facts: ['DISK_FULL', 'src/critical.ts:99'],
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
// ── Runner ───────────────────────────────────────────────────────────────────
|
|
79
|
+
export function runBench(fixtures = FIXTURES) {
|
|
80
|
+
const rows = fixtures.map(f => {
|
|
81
|
+
const compressed = preprocessForTool(f.content, f.tool, 0);
|
|
82
|
+
const originalChars = f.content.length;
|
|
83
|
+
const compressedChars = compressed.length;
|
|
84
|
+
const compressionPct = originalChars > 0 ? Math.max(0, ((originalChars - compressedChars) / originalChars) * 100) : 0;
|
|
85
|
+
return {
|
|
86
|
+
name: f.name,
|
|
87
|
+
originalChars,
|
|
88
|
+
compressedChars,
|
|
89
|
+
compressionPct: Math.round(compressionPct * 10) / 10,
|
|
90
|
+
inlineRecall: factRecall(compressed, f.facts),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
const n = rows.length || 1;
|
|
94
|
+
return {
|
|
95
|
+
rows,
|
|
96
|
+
avgCompressionPct: Math.round((rows.reduce((s, r) => s + r.compressionPct, 0) / n) * 10) / 10,
|
|
97
|
+
avgInlineRecall: Math.round((rows.reduce((s, r) => s + r.inlineRecall, 0) / n) * 100) / 100,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Human-readable table for the CLI. */
|
|
101
|
+
export function formatBench(summary) {
|
|
102
|
+
const lines = [];
|
|
103
|
+
lines.push('Squeezr compression + fact-recall benchmark');
|
|
104
|
+
lines.push('(recoverability is 100% by design via squeezr_expand; inline recall = answerable without expanding)');
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push('fixture orig comp saved inline-recall');
|
|
107
|
+
lines.push('─'.repeat(78));
|
|
108
|
+
for (const r of summary.rows) {
|
|
109
|
+
lines.push(`${r.name.padEnd(36)} ${String(r.originalChars).padStart(6)} ${String(r.compressedChars).padStart(6)} ${(r.compressionPct + '%').padStart(6)} ${Math.round(r.inlineRecall * 100) + '%'}`);
|
|
110
|
+
}
|
|
111
|
+
lines.push('─'.repeat(78));
|
|
112
|
+
lines.push(`AVG: ${summary.avgCompressionPct}% compression · ${Math.round(summary.avgInlineRecall * 100)}% inline fact recall · 100% recoverable`);
|
|
113
|
+
return lines.join('\n');
|
|
114
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CacheAligner (detector-only) — Squeezr's take on headroom's CacheAligner.
|
|
3
|
+
*
|
|
4
|
+
* Anthropic bills the cached prefix at 0.1x, but ONLY if the prefix arrives byte-for-byte
|
|
5
|
+
* identical between requests. If the CLIENT's own system prompt embeds volatile content
|
|
6
|
+
* (a per-session UUID, a live timestamp, a build hash, a JWT) INSIDE the cached region,
|
|
7
|
+
* that prefix changes every request and the cache never hits — re-billing the whole
|
|
8
|
+
* context at full price. That is exactly the failure that once burned a 5h plan.
|
|
9
|
+
*
|
|
10
|
+
* Squeezr already monitors cache hit-health %; this tells you the likely CAUSE. It is a
|
|
11
|
+
* pure DETECTOR: it never mutates the prompt (mutating the cache hot zone is itself a
|
|
12
|
+
* cache-buster). It just surfaces "your prefix is unstable because of X".
|
|
13
|
+
*
|
|
14
|
+
* Detection is structural (shape-based regex over known volatile token forms), not
|
|
15
|
+
* semantic. Order matters: JWTs are matched before hashes so a JWT segment isn't
|
|
16
|
+
* miscounted as a hex hash.
|
|
17
|
+
*/
|
|
18
|
+
export type VolatileKind = 'uuid' | 'timestamp' | 'jwt' | 'hash';
|
|
19
|
+
export interface VolatileFinding {
|
|
20
|
+
kind: VolatileKind;
|
|
21
|
+
count: number;
|
|
22
|
+
}
|
|
23
|
+
/** Structural scan for volatile tokens. Matched kinds are masked out before later
|
|
24
|
+
* patterns run, so a JWT is never also counted as a hash. */
|
|
25
|
+
export declare function detectVolatile(text: string): VolatileFinding[];
|
|
26
|
+
export interface SystemAnalysis {
|
|
27
|
+
hasVolatile: boolean;
|
|
28
|
+
findings: VolatileFinding[];
|
|
29
|
+
}
|
|
30
|
+
export declare function analyzeSystemPrompt(system: unknown): SystemAnalysis;
|
|
31
|
+
export declare function formatVolatileWarning(findings: VolatileFinding[]): string;
|
|
32
|
+
/** Warn at most once per distinct finding signature. Returns true iff it warned now. */
|
|
33
|
+
export declare function warnIfVolatile(system: unknown): boolean;
|
|
34
|
+
export declare function _resetWarnedForTest(): void;
|