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
package/dist/compressor.js
CHANGED
|
@@ -206,10 +206,12 @@ async function compressWithGptMini(text, apiKey, extra) {
|
|
|
206
206
|
return resp.choices[0].message.content ?? '';
|
|
207
207
|
}
|
|
208
208
|
async function compressWithGeminiFlash(text, apiKey, extra) {
|
|
209
|
-
|
|
209
|
+
// Pass the key in the x-goog-api-key header, never in the URL query string:
|
|
210
|
+
// URLs leak into logs, proxies and error traces where a secret must not appear.
|
|
211
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_FLASH_MODEL}:generateContent`;
|
|
210
212
|
const resp = await fetch(url, {
|
|
211
213
|
method: 'POST',
|
|
212
|
-
headers: { 'content-type': 'application/json' },
|
|
214
|
+
headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
|
|
213
215
|
body: JSON.stringify({
|
|
214
216
|
contents: [{ role: 'user', parts: [{ text: `${promptWith(extra)}\n\n---\n${text.slice(0, OLLAMA_SAFE_INPUT)}` }] }],
|
|
215
217
|
}),
|
|
@@ -663,6 +665,25 @@ function buildAnthropicToolIdMap(messages) {
|
|
|
663
665
|
}
|
|
664
666
|
return { nameMap, skipIds };
|
|
665
667
|
}
|
|
668
|
+
// Most recent user-typed text across the conversation (skips tool-result-only turns),
|
|
669
|
+
// used as the relevance query for deterministic crushing. Capped so it stays cheap.
|
|
670
|
+
function lastUserText(messages) {
|
|
671
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
672
|
+
const m = messages[i];
|
|
673
|
+
if (m.role !== 'user')
|
|
674
|
+
continue;
|
|
675
|
+
if (typeof m.content === 'string')
|
|
676
|
+
return m.content.slice(0, 2000);
|
|
677
|
+
if (Array.isArray(m.content)) {
|
|
678
|
+
const texts = m.content
|
|
679
|
+
.filter(b => b && b.type === 'text' && typeof b.text === 'string')
|
|
680
|
+
.map(b => b.text);
|
|
681
|
+
if (texts.length > 0)
|
|
682
|
+
return texts.join(' ').slice(0, 2000);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return '';
|
|
686
|
+
}
|
|
666
687
|
export async function compressAnthropicMessages(messages, apiKey, config, systemExtraChars = 0) {
|
|
667
688
|
if (config.disabled)
|
|
668
689
|
return [messages, emptySavings()];
|
|
@@ -792,12 +813,19 @@ export async function compressAnthropicMessages(messages, apiKey, config, system
|
|
|
792
813
|
// changes every turn = cache miss = the 2026-06-04 over-bill). One cache miss
|
|
793
814
|
// happens the first time the level changes; stable forever after.
|
|
794
815
|
const DET_PRESSURE = 0;
|
|
816
|
+
// BM25 relevance in the deterministic pass is QUERY-dependent → it changes when the
|
|
817
|
+
// user's task changes. That is ONLY cache-safe where the output is not part of a
|
|
818
|
+
// byte-stable cached prefix. So we feed a query ONLY when there are NO cache markers
|
|
819
|
+
// (non-caching clients); for cache-marker clients (Claude Code) query = '' keeps the
|
|
820
|
+
// pass byte-identical between requests. (Relevance for the cached path needs per-block
|
|
821
|
+
// content-hash freezing — a follow-up.)
|
|
822
|
+
const detQuery = hasCacheMarkers ? '' : lastUserText(messages);
|
|
795
823
|
const detT0 = Date.now();
|
|
796
824
|
let detSaved = 0;
|
|
797
825
|
for (const { index, subIndex, text, tool } of allResults) {
|
|
798
826
|
if (dedupedSet.has(`${index}:${subIndex}`))
|
|
799
827
|
continue; // already replaced by dedup
|
|
800
|
-
const det = preprocessForTool(text, tool, DET_PRESSURE);
|
|
828
|
+
const det = preprocessForTool(text, tool, DET_PRESSURE, detQuery);
|
|
801
829
|
if (det !== text) {
|
|
802
830
|
;
|
|
803
831
|
msgs[index].content[subIndex].content = det;
|
|
@@ -886,7 +914,17 @@ export async function compressAnthropicMessages(messages, apiKey, config, system
|
|
|
886
914
|
// Write's content. These are huge and previously unprocessed. Skip the most
|
|
887
915
|
// recent N assistant turns (`keepRecentAssistant`) so the live tool call
|
|
888
916
|
// stays intact.
|
|
889
|
-
|
|
917
|
+
//
|
|
918
|
+
// DEFAULT OFF (compressToolInputs = false). Unlike tool RESULTS, tool INPUTS are
|
|
919
|
+
// model-authored code that must survive verbatim: Write.content is written to disk
|
|
920
|
+
// byte-for-byte, and Edit.old_string must byte-match the file for the edit to apply.
|
|
921
|
+
// The deterministic pipeline (line dedup, whitespace collapse+trim, JSON minify,
|
|
922
|
+
// timestamp strip) mutates those bytes. Because Squeezr rewrites history, the model
|
|
923
|
+
// then sees its OWN past Write as "body: ... [repeated 4 more times]" with dropped
|
|
924
|
+
// braces/`>`, and propagates that corruption to disk on the next edit. Old-turn
|
|
925
|
+
// bloat is reclaimed safely by stale_turns (whole-turn collapse) instead. Opt back
|
|
926
|
+
// in with compression.compress_tool_inputs = true only if you accept that risk.
|
|
927
|
+
if (config.compressConversation && config.compressToolInputs) {
|
|
890
928
|
const toolUses = extractAnthropicAssistantToolUses(msgs, config.keepRecentAssistant);
|
|
891
929
|
let tuSaved = 0;
|
|
892
930
|
let tuCount = 0;
|
package/dist/config.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class Config {
|
|
|
9
9
|
readonly aiCompression: boolean;
|
|
10
10
|
readonly compressSystemPrompt: boolean;
|
|
11
11
|
readonly compressConversation: boolean;
|
|
12
|
+
readonly compressToolInputs: boolean;
|
|
12
13
|
readonly captureRequests: boolean;
|
|
13
14
|
readonly captureLimit: number;
|
|
14
15
|
readonly staleTurns: boolean;
|
|
@@ -42,6 +43,11 @@ export declare class Config {
|
|
|
42
43
|
readonly localUpstreamUrl: string;
|
|
43
44
|
readonly localCompressionModel: string;
|
|
44
45
|
readonly localDummyKeys: Set<string>;
|
|
46
|
+
readonly outputShaperEnabled: boolean;
|
|
47
|
+
readonly outputVerbositySteering: boolean;
|
|
48
|
+
readonly outputLevel: 1 | 2 | 3 | 4;
|
|
49
|
+
readonly outputEffortRouting: boolean;
|
|
50
|
+
readonly outputMechanicalThinkingFloor: number;
|
|
45
51
|
constructor();
|
|
46
52
|
thresholdForPressure(pressure: number): number;
|
|
47
53
|
shouldSkipTool(toolName: string): boolean;
|
package/dist/config.js
CHANGED
|
@@ -87,6 +87,7 @@ export class Config {
|
|
|
87
87
|
aiCompression;
|
|
88
88
|
compressSystemPrompt;
|
|
89
89
|
compressConversation;
|
|
90
|
+
compressToolInputs;
|
|
90
91
|
captureRequests;
|
|
91
92
|
captureLimit;
|
|
92
93
|
staleTurns;
|
|
@@ -120,6 +121,11 @@ export class Config {
|
|
|
120
121
|
localUpstreamUrl;
|
|
121
122
|
localCompressionModel;
|
|
122
123
|
localDummyKeys;
|
|
124
|
+
outputShaperEnabled;
|
|
125
|
+
outputVerbositySteering;
|
|
126
|
+
outputLevel;
|
|
127
|
+
outputEffortRouting;
|
|
128
|
+
outputMechanicalThinkingFloor;
|
|
123
129
|
constructor() {
|
|
124
130
|
const t = loadToml();
|
|
125
131
|
const p = t.proxy ?? {};
|
|
@@ -127,6 +133,7 @@ export class Config {
|
|
|
127
133
|
const ca = t.cache ?? {};
|
|
128
134
|
const ad = t.adaptive ?? {};
|
|
129
135
|
const lo = t.local ?? {};
|
|
136
|
+
const ou = t.output ?? {};
|
|
130
137
|
this.port = parseInt(env('SQUEEZR_PORT', String(p.port ?? 8080)));
|
|
131
138
|
this.mitmPort = parseInt(env('SQUEEZR_MITM_PORT', String(p.mitm_port ?? this.port + 1)));
|
|
132
139
|
this.threshold = parseInt(env('SQUEEZR_THRESHOLD', String(c.threshold ?? 800)));
|
|
@@ -140,6 +147,13 @@ export class Config {
|
|
|
140
147
|
// compress_system_prompt also makes a Haiku call — gate it behind aiCompression too.
|
|
141
148
|
this.compressSystemPrompt = (c.compress_system_prompt ?? true) && this.aiCompression;
|
|
142
149
|
this.compressConversation = c.compress_conversation ?? true; // safe by default — only deterministic on assistant msgs
|
|
150
|
+
// Lossy-clean OLD tool_use INPUTS (Write.content, Edit.old/new_string, Bash.command).
|
|
151
|
+
// DEFAULT FALSE: these are model-authored code that round-trips to disk verbatim
|
|
152
|
+
// (Write) or must byte-match disk (Edit old_string). Running dedup/whitespace/JSON
|
|
153
|
+
// minify over them folded repeated lines into "... [repeated N more times]", dropped
|
|
154
|
+
// braces and JSX `>`, and .trim()'d file bodies — corruption that reached disk when
|
|
155
|
+
// the model later re-edited from its now-mangled view of its own past writes.
|
|
156
|
+
this.compressToolInputs = c.compress_tool_inputs ?? false;
|
|
143
157
|
this.captureRequests = c.capture_requests ?? false;
|
|
144
158
|
this.captureLimit = c.capture_limit ?? 20;
|
|
145
159
|
this.staleTurns = c.stale_turns ?? true;
|
|
@@ -183,6 +197,17 @@ export class Config {
|
|
|
183
197
|
this.localCompressionModel = env('SQUEEZR_LOCAL_MODEL', lo.compression_model ?? 'qwen2.5-coder:1.5b');
|
|
184
198
|
const rawDummies = lo.dummy_keys ?? ['ollama', 'lm-studio', 'sk-no-key-required', 'local', 'none', ''];
|
|
185
199
|
this.localDummyKeys = new Set(rawDummies.map(k => k.toLowerCase()));
|
|
200
|
+
// Output-side token reduction. DEFAULT OFF — opt-in, like all levers that
|
|
201
|
+
// reshape behaviour rather than just clean tool output. Enable via TOML
|
|
202
|
+
// [output] enabled = true, or env SQUEEZR_OUTPUT_SHAPER=1. Both sub-levers
|
|
203
|
+
// (verbosity steering, effort routing) default ON once the master is on.
|
|
204
|
+
this.outputShaperEnabled =
|
|
205
|
+
env('SQUEEZR_OUTPUT_SHAPER', '') === '1' || env('SQUEEZR_OUTPUT_SHAPER', '') === 'true' || (ou.enabled ?? false);
|
|
206
|
+
this.outputVerbositySteering = ou.verbosity_steering ?? true;
|
|
207
|
+
const lvl = ou.level ?? 2;
|
|
208
|
+
this.outputLevel = (lvl >= 1 && lvl <= 4 ? lvl : 2);
|
|
209
|
+
this.outputEffortRouting = ou.effort_routing ?? true;
|
|
210
|
+
this.outputMechanicalThinkingFloor = ou.mechanical_thinking_floor ?? 1024;
|
|
186
211
|
}
|
|
187
212
|
thresholdForPressure(pressure) {
|
|
188
213
|
if (!this.adaptiveEnabled)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content router — extends the JSON crusher's reach from "the WHOLE tool result is a JSON
|
|
3
|
+
* array" to "a JSON array appears ANYWHERE inside the tool result". Squeezr's take on the
|
|
4
|
+
* routing idea behind headroom's ContentRouter.
|
|
5
|
+
*
|
|
6
|
+
* A lot of structured data arrives wrapped: `gh api` with a status line, an MCP tool that
|
|
7
|
+
* returns a labelled envelope, a bash command that echoes a header then the JSON. Before,
|
|
8
|
+
* jsonCrush only fired on a pure-JSON result, so those were missed. This locates embedded
|
|
9
|
+
* arrays-of-objects and crushes each in place, leaving the surrounding text untouched.
|
|
10
|
+
*
|
|
11
|
+
* Safety: candidate spans are found by balanced-bracket scanning (string/escape aware),
|
|
12
|
+
* but the real safety net is that each span is handed to crushJsonArrays, which JSON.parses
|
|
13
|
+
* it — a false-positive span that isn't valid JSON simply doesn't crush and is left verbatim.
|
|
14
|
+
* Deterministic → cache-safe. Each crushed span is self-recoverable via its own expand id.
|
|
15
|
+
*/
|
|
16
|
+
import { TABLE_MARKER_RE } from './jsonCrush.js';
|
|
17
|
+
export { TABLE_MARKER_RE };
|
|
18
|
+
/** Non-overlapping, top-level spans of `[ {...`-style arrays (arrays of objects). */
|
|
19
|
+
export declare function findObjectArraySpans(text: string): Array<{
|
|
20
|
+
start: number;
|
|
21
|
+
end: number;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Crush every embedded array-of-objects in `text`, splicing each crushed table back in and
|
|
25
|
+
* leaving the surrounding text verbatim. Returns the new text and total chars saved.
|
|
26
|
+
*/
|
|
27
|
+
export declare function crushEmbeddedJson(text: string): {
|
|
28
|
+
text: string;
|
|
29
|
+
savedChars: number;
|
|
30
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content router — extends the JSON crusher's reach from "the WHOLE tool result is a JSON
|
|
3
|
+
* array" to "a JSON array appears ANYWHERE inside the tool result". Squeezr's take on the
|
|
4
|
+
* routing idea behind headroom's ContentRouter.
|
|
5
|
+
*
|
|
6
|
+
* A lot of structured data arrives wrapped: `gh api` with a status line, an MCP tool that
|
|
7
|
+
* returns a labelled envelope, a bash command that echoes a header then the JSON. Before,
|
|
8
|
+
* jsonCrush only fired on a pure-JSON result, so those were missed. This locates embedded
|
|
9
|
+
* arrays-of-objects and crushes each in place, leaving the surrounding text untouched.
|
|
10
|
+
*
|
|
11
|
+
* Safety: candidate spans are found by balanced-bracket scanning (string/escape aware),
|
|
12
|
+
* but the real safety net is that each span is handed to crushJsonArrays, which JSON.parses
|
|
13
|
+
* it — a false-positive span that isn't valid JSON simply doesn't crush and is left verbatim.
|
|
14
|
+
* Deterministic → cache-safe. Each crushed span is self-recoverable via its own expand id.
|
|
15
|
+
*/
|
|
16
|
+
import { crushJsonArrays, TABLE_MARKER_RE } from './jsonCrush.js';
|
|
17
|
+
export { TABLE_MARKER_RE };
|
|
18
|
+
/** Scan from an opening '[' to its matching ']' (exclusive end), string/escape aware.
|
|
19
|
+
* Returns -1 if unbalanced. */
|
|
20
|
+
function scanArray(text, start) {
|
|
21
|
+
let depth = 0;
|
|
22
|
+
let inStr = false;
|
|
23
|
+
for (let i = start; i < text.length; i++) {
|
|
24
|
+
const c = text[i];
|
|
25
|
+
if (inStr) {
|
|
26
|
+
if (c === '\\') {
|
|
27
|
+
i++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (c === '"')
|
|
31
|
+
inStr = false;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (c === '"') {
|
|
35
|
+
inStr = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (c === '[')
|
|
39
|
+
depth++;
|
|
40
|
+
else if (c === ']') {
|
|
41
|
+
depth--;
|
|
42
|
+
if (depth === 0)
|
|
43
|
+
return i + 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
/** Non-overlapping, top-level spans of `[ {...`-style arrays (arrays of objects). */
|
|
49
|
+
export function findObjectArraySpans(text) {
|
|
50
|
+
const spans = [];
|
|
51
|
+
const n = text.length;
|
|
52
|
+
let i = 0;
|
|
53
|
+
let inStr = false;
|
|
54
|
+
while (i < n) {
|
|
55
|
+
const c = text[i];
|
|
56
|
+
if (inStr) {
|
|
57
|
+
if (c === '\\') {
|
|
58
|
+
i += 2;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (c === '"')
|
|
62
|
+
inStr = false;
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (c === '"') {
|
|
67
|
+
inStr = true;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (c === '[') {
|
|
72
|
+
// peek past whitespace for '{' → array of objects
|
|
73
|
+
let j = i + 1;
|
|
74
|
+
while (j < n && /\s/.test(text[j]))
|
|
75
|
+
j++;
|
|
76
|
+
if (text[j] === '{') {
|
|
77
|
+
const end = scanArray(text, i);
|
|
78
|
+
if (end > i) {
|
|
79
|
+
spans.push({ start: i, end });
|
|
80
|
+
i = end;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
return spans;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Crush every embedded array-of-objects in `text`, splicing each crushed table back in and
|
|
91
|
+
* leaving the surrounding text verbatim. Returns the new text and total chars saved.
|
|
92
|
+
*/
|
|
93
|
+
export function crushEmbeddedJson(text) {
|
|
94
|
+
const spans = findObjectArraySpans(text);
|
|
95
|
+
if (spans.length === 0)
|
|
96
|
+
return { text, savedChars: 0 };
|
|
97
|
+
let out = '';
|
|
98
|
+
let last = 0;
|
|
99
|
+
let saved = 0;
|
|
100
|
+
for (const { start, end } of spans) {
|
|
101
|
+
out += text.slice(last, start);
|
|
102
|
+
const sub = text.slice(start, end);
|
|
103
|
+
const crushed = crushJsonArrays(sub);
|
|
104
|
+
out += crushed.text;
|
|
105
|
+
saved += crushed.savedChars;
|
|
106
|
+
last = end;
|
|
107
|
+
}
|
|
108
|
+
out += text.slice(last);
|
|
109
|
+
if (saved <= 0)
|
|
110
|
+
return { text, savedChars: 0 };
|
|
111
|
+
return { text: out, savedChars: saved };
|
|
112
|
+
}
|