squeezr-ai 1.81.2 → 1.99.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.
- 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__/compressor.test.js +5 -1
- package/dist/__tests__/contentRouter.test.d.ts +1 -0
- package/dist/__tests__/contentRouter.test.js +76 -0
- package/dist/__tests__/controlEndpointGuard.test.d.ts +1 -0
- package/dist/__tests__/controlEndpointGuard.test.js +61 -0
- package/dist/__tests__/deterministic.test.js +13 -0
- package/dist/__tests__/doctor.test.d.ts +1 -0
- package/dist/__tests__/doctor.test.js +86 -0
- package/dist/__tests__/jsonCrush.test.d.ts +1 -0
- package/dist/__tests__/jsonCrush.test.js +168 -0
- package/dist/__tests__/learn.test.d.ts +1 -0
- package/dist/__tests__/learn.test.js +159 -0
- package/dist/__tests__/outputSavings.test.d.ts +1 -0
- package/dist/__tests__/outputSavings.test.js +49 -0
- package/dist/__tests__/outputShaper.test.d.ts +1 -0
- package/dist/__tests__/outputShaper.test.js +216 -0
- package/dist/__tests__/relevance.test.d.ts +1 -0
- package/dist/__tests__/relevance.test.js +66 -0
- package/dist/__tests__/secureKeyFile.test.d.ts +1 -0
- package/dist/__tests__/secureKeyFile.test.js +27 -0
- package/dist/__tests__/serverBinding.test.d.ts +1 -0
- package/dist/__tests__/serverBinding.test.js +18 -0
- package/dist/__tests__/statsOutput.test.d.ts +1 -0
- package/dist/__tests__/statsOutput.test.js +46 -0
- package/dist/__tests__/textCrusher.test.d.ts +1 -0
- package/dist/__tests__/textCrusher.test.js +133 -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/codexMitm.d.ts +1 -0
- package/dist/codexMitm.js +26 -2
- package/dist/compressor.js +42 -4
- package/dist/config.d.ts +6 -0
- package/dist/config.js +25 -0
- package/dist/contentRouter.d.ts +30 -0
- package/dist/contentRouter.js +112 -0
- package/dist/dashboard.d.ts +1 -1
- package/dist/dashboard.js +26 -7
- package/dist/deterministic.d.ts +4 -1
- package/dist/deterministic.js +43 -10
- package/dist/doctor.d.ts +38 -0
- package/dist/doctor.js +113 -0
- package/dist/index.js +4 -1
- package/dist/jsonCrush.d.ts +34 -0
- package/dist/jsonCrush.js +177 -0
- package/dist/learn.d.ts +65 -0
- package/dist/learn.js +244 -0
- package/dist/outputSavings.d.ts +20 -0
- package/dist/outputSavings.js +52 -0
- package/dist/outputShaper.d.ts +71 -0
- package/dist/outputShaper.js +155 -0
- package/dist/relevance.d.ts +27 -0
- package/dist/relevance.js +78 -0
- package/dist/server.js +106 -11
- package/dist/stats.d.ts +17 -0
- package/dist/stats.js +55 -0
- package/dist/systemPrompt.js +3 -2
- package/dist/textCrusher.d.ts +35 -0
- package/dist/textCrusher.js +160 -0
- package/package.json +69 -69
- package/squeezr.toml +15 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { crushJsonArrays, TABLE_MARKER_RE, simhash32, hammingDistance } from '../jsonCrush.js';
|
|
3
|
+
import { retrieveOriginal } from '../expand.js';
|
|
4
|
+
function makeRows(n) {
|
|
5
|
+
const rows = [];
|
|
6
|
+
for (let i = 0; i < n; i++) {
|
|
7
|
+
rows.push({ id: i, name: `service-${i}`, status: i % 2 === 0 ? 'running' : 'stopped', region: 'us-east-1' });
|
|
8
|
+
}
|
|
9
|
+
return JSON.stringify(rows);
|
|
10
|
+
}
|
|
11
|
+
describe('crushJsonArrays', () => {
|
|
12
|
+
it('crushes a uniform array of >=5 objects and saves chars', () => {
|
|
13
|
+
const input = makeRows(20);
|
|
14
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
15
|
+
expect(savedChars).toBeGreaterThan(0);
|
|
16
|
+
expect(text.length).toBeLessThan(input.length);
|
|
17
|
+
expect(TABLE_MARKER_RE.test(text)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
it('embeds a recoverable squeezr_expand id that returns the ORIGINAL json', () => {
|
|
20
|
+
const input = makeRows(20);
|
|
21
|
+
const { text } = crushJsonArrays(input);
|
|
22
|
+
const m = text.match(TABLE_MARKER_RE);
|
|
23
|
+
expect(m).not.toBeNull();
|
|
24
|
+
const id = m[1];
|
|
25
|
+
expect(retrieveOriginal(id)).toBe(input);
|
|
26
|
+
});
|
|
27
|
+
it('keeps every value present in the table body (lossless representation)', () => {
|
|
28
|
+
const input = makeRows(6);
|
|
29
|
+
const { text } = crushJsonArrays(input);
|
|
30
|
+
expect(text.includes('service-0')).toBe(true);
|
|
31
|
+
expect(text.includes('service-5')).toBe(true);
|
|
32
|
+
expect(text.includes('running')).toBe(true);
|
|
33
|
+
expect(text.includes('stopped')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('lists the column names once in the header, not per row', () => {
|
|
36
|
+
const input = makeRows(10);
|
|
37
|
+
const { text } = crushJsonArrays(input);
|
|
38
|
+
// "region" appears once as a column header, never repeated as a JSON key
|
|
39
|
+
expect((text.match(/"region"/g) ?? []).length).toBe(0);
|
|
40
|
+
expect(text.includes('region')).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('does NOT crush arrays below the minimum item count', () => {
|
|
43
|
+
const input = JSON.stringify([{ a: 1 }, { a: 2 }]);
|
|
44
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
45
|
+
expect(savedChars).toBe(0);
|
|
46
|
+
expect(text).toBe(input);
|
|
47
|
+
});
|
|
48
|
+
it('does NOT crush a single JSON object', () => {
|
|
49
|
+
const input = JSON.stringify({ id: 1, name: 'x', status: 'ok', region: 'eu' });
|
|
50
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
51
|
+
expect(savedChars).toBe(0);
|
|
52
|
+
expect(text).toBe(input);
|
|
53
|
+
});
|
|
54
|
+
it('does NOT crush an array of scalars (no keys to factor out)', () => {
|
|
55
|
+
const input = JSON.stringify([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
56
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
57
|
+
expect(savedChars).toBe(0);
|
|
58
|
+
expect(text).toBe(input);
|
|
59
|
+
});
|
|
60
|
+
it('leaves non-JSON text unchanged', () => {
|
|
61
|
+
const input = 'this is just a log line\nand another one';
|
|
62
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
63
|
+
expect(savedChars).toBe(0);
|
|
64
|
+
expect(text).toBe(input);
|
|
65
|
+
});
|
|
66
|
+
it('does NOT crush when the columnar form would not save enough', () => {
|
|
67
|
+
// few rows, tiny keys → little repeated-key overhead → not worth it
|
|
68
|
+
const input = JSON.stringify([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }]);
|
|
69
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
70
|
+
expect(savedChars).toBe(0);
|
|
71
|
+
expect(text).toBe(input);
|
|
72
|
+
});
|
|
73
|
+
it('handles heterogeneous objects (missing keys) and stays recoverable', () => {
|
|
74
|
+
const arr = [
|
|
75
|
+
{ id: 1, name: 'a', status: 'ok', extra: 'z' },
|
|
76
|
+
{ id: 2, name: 'b', status: 'ok' },
|
|
77
|
+
{ id: 3, name: 'c', status: 'fail', region: 'eu' },
|
|
78
|
+
{ id: 4, name: 'd', status: 'ok' },
|
|
79
|
+
{ id: 5, name: 'e', status: 'ok' },
|
|
80
|
+
{ id: 6, name: 'f', status: 'ok' },
|
|
81
|
+
];
|
|
82
|
+
const input = JSON.stringify(arr);
|
|
83
|
+
const { text } = crushJsonArrays(input);
|
|
84
|
+
const m = text.match(TABLE_MARKER_RE);
|
|
85
|
+
expect(m).not.toBeNull();
|
|
86
|
+
expect(retrieveOriginal(m[1])).toBe(input);
|
|
87
|
+
});
|
|
88
|
+
it('renders nested objects inline as compact JSON', () => {
|
|
89
|
+
const arr = Array.from({ length: 6 }, (_, i) => ({
|
|
90
|
+
id: i,
|
|
91
|
+
meta: { region: 'us', tier: 'gold' },
|
|
92
|
+
name: `n${i}`,
|
|
93
|
+
}));
|
|
94
|
+
const input = JSON.stringify(arr);
|
|
95
|
+
const { text } = crushJsonArrays(input);
|
|
96
|
+
expect(text.includes('{"region":"us","tier":"gold"}')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
it('is deterministic — same input yields byte-identical output (cache-safe)', () => {
|
|
99
|
+
const input = makeRows(15);
|
|
100
|
+
const a = crushJsonArrays(input).text;
|
|
101
|
+
const b = crushJsonArrays(input).text;
|
|
102
|
+
expect(a).toBe(b);
|
|
103
|
+
});
|
|
104
|
+
// ── Lossy row-drop (SimHash near-dup collapse) for large arrays ──────────────
|
|
105
|
+
function manyRows(n) {
|
|
106
|
+
return Array.from({ length: n }, (_, i) => ({
|
|
107
|
+
id: i,
|
|
108
|
+
name: `svc-${i}`,
|
|
109
|
+
status: i % 2 === 0 ? 'running' : 'stopped',
|
|
110
|
+
region: 'us-east-1',
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
it('drops near-duplicate rows in a large array but keeps the original recoverable', () => {
|
|
114
|
+
const rows = manyRows(120);
|
|
115
|
+
const input = JSON.stringify(rows);
|
|
116
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
117
|
+
expect(savedChars).toBeGreaterThan(0);
|
|
118
|
+
// the inline table shows far fewer than 120 rows
|
|
119
|
+
const bodyLines = text.split('\n').filter(l => l.includes('\t'));
|
|
120
|
+
expect(bodyLines.length).toBeLessThan(120);
|
|
121
|
+
// an omitted-rows note is present
|
|
122
|
+
expect(/of 120 rows|rows omitted|near-duplicate/i.test(text)).toBe(true);
|
|
123
|
+
// ALL 120 rows still recoverable via expand
|
|
124
|
+
const m = text.match(TABLE_MARKER_RE);
|
|
125
|
+
expect(JSON.parse(retrieveOriginal(m[1])).length).toBe(120);
|
|
126
|
+
});
|
|
127
|
+
it('ALWAYS keeps an anomalous/error row inline even amid near-duplicates', () => {
|
|
128
|
+
const rows = manyRows(120);
|
|
129
|
+
rows[63] = { id: 63, name: 'svc-63', status: 'CrashLoopBackOff', region: 'us-east-1', error: 'OOMKilled' };
|
|
130
|
+
const input = JSON.stringify(rows);
|
|
131
|
+
const { text } = crushJsonArrays(input);
|
|
132
|
+
expect(text.includes('CrashLoopBackOff')).toBe(true);
|
|
133
|
+
expect(text.includes('OOMKilled')).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
it('keeps ALL rows for arrays below the lossy threshold (no row-drop)', () => {
|
|
136
|
+
const input = JSON.stringify(manyRows(30));
|
|
137
|
+
const { text } = crushJsonArrays(input);
|
|
138
|
+
expect(/rows omitted|near-duplicate/i.test(text)).toBe(false);
|
|
139
|
+
// lines after the marker (0) and header (1) are the data rows
|
|
140
|
+
const dataRows = text.split('\n').slice(2);
|
|
141
|
+
expect(dataRows.length).toBe(30);
|
|
142
|
+
});
|
|
143
|
+
it('row-drop is deterministic', () => {
|
|
144
|
+
const input = JSON.stringify(manyRows(150));
|
|
145
|
+
expect(crushJsonArrays(input).text).toBe(crushJsonArrays(input).text);
|
|
146
|
+
});
|
|
147
|
+
it('crushes a pretty-printed (indented) JSON array too', () => {
|
|
148
|
+
const input = JSON.stringify(Array.from({ length: 8 }, (_, i) => ({ id: i, name: `x${i}`, status: 'ok', region: 'us' })), null, 2);
|
|
149
|
+
const { text, savedChars } = crushJsonArrays(input);
|
|
150
|
+
expect(savedChars).toBeGreaterThan(0);
|
|
151
|
+
expect(TABLE_MARKER_RE.test(text)).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('simhash32 / hammingDistance', () => {
|
|
155
|
+
it('identical text → identical fingerprint (distance 0)', () => {
|
|
156
|
+
expect(hammingDistance(simhash32('the quick brown fox'), simhash32('the quick brown fox'))).toBe(0);
|
|
157
|
+
});
|
|
158
|
+
it('near-identical rows are closer than unrelated ones (the property that matters)', () => {
|
|
159
|
+
// Realistic row strings (many shared tokens) — SimHash's signal is strongest here.
|
|
160
|
+
const base = simhash32('id 1 name svc-1 status running region us-east-1 node ip-10-0-1-5');
|
|
161
|
+
const near = simhash32('id 2 name svc-2 status running region us-east-1 node ip-10-0-1-5');
|
|
162
|
+
const far = simhash32('completely unrelated payload about quarterly revenue and marketing spend');
|
|
163
|
+
expect(hammingDistance(base, near)).toBeLessThan(hammingDistance(base, far));
|
|
164
|
+
});
|
|
165
|
+
it('is deterministic', () => {
|
|
166
|
+
expect(simhash32('same input here')).toBe(simhash32('same input here'));
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,49 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { wordNgrams, echoRatio, extractAssistantTextFromSse } from '../outputSavings.js';
|
|
3
|
+
describe('wordNgrams', () => {
|
|
4
|
+
it('builds word n-grams', () => {
|
|
5
|
+
expect(wordNgrams('a b c d', 3).has('a b c')).toBe(true);
|
|
6
|
+
expect(wordNgrams('a b c d', 3).has('b c d')).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
it('empty for text shorter than n', () => {
|
|
9
|
+
expect(wordNgrams('a b', 3).size).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
describe('echoRatio', () => {
|
|
13
|
+
it('~1 when the output only restates context', () => {
|
|
14
|
+
const context = 'the authentication middleware refreshes the token on every request cycle';
|
|
15
|
+
const output = 'the authentication middleware refreshes the token on every request cycle';
|
|
16
|
+
expect(echoRatio(output, context)).toBeGreaterThan(0.9);
|
|
17
|
+
});
|
|
18
|
+
it('~0 when the output is entirely novel', () => {
|
|
19
|
+
const context = 'the authentication middleware refreshes the token';
|
|
20
|
+
const output = 'completely different sentence about unrelated banana harvest logistics today';
|
|
21
|
+
expect(echoRatio(output, context)).toBeLessThan(0.1);
|
|
22
|
+
});
|
|
23
|
+
it('is 0 for empty output', () => {
|
|
24
|
+
expect(echoRatio('', 'some context here at all')).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
it('is between 0 and 1 for partial restating', () => {
|
|
27
|
+
const context = 'shared preamble tokens appear here in the context block';
|
|
28
|
+
const output = 'shared preamble tokens appear here plus a brand new original tail clause';
|
|
29
|
+
const r = echoRatio(output, context);
|
|
30
|
+
expect(r).toBeGreaterThan(0);
|
|
31
|
+
expect(r).toBeLessThan(1);
|
|
32
|
+
});
|
|
33
|
+
it('is deterministic', () => {
|
|
34
|
+
expect(echoRatio('a b c d e', 'a b c d e f')).toBe(echoRatio('a b c d e', 'a b c d e f'));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('extractAssistantTextFromSse', () => {
|
|
38
|
+
it('concatenates text_delta events, unescaping', () => {
|
|
39
|
+
const sse = [
|
|
40
|
+
'event: content_block_delta',
|
|
41
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello "}}',
|
|
42
|
+
'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"world\\n"}}',
|
|
43
|
+
].join('\n');
|
|
44
|
+
expect(extractAssistantTextFromSse(sse)).toBe('Hello world\n');
|
|
45
|
+
});
|
|
46
|
+
it('returns empty string when there are no text deltas', () => {
|
|
47
|
+
expect(extractAssistantTextFromSse('data: {"type":"message_start"}')).toBe('');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -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 {};
|