squeezr-ai 1.90.0 → 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.
Files changed (43) hide show
  1. package/dist/__tests__/compressor.test.js +5 -1
  2. package/dist/__tests__/contentRouter.test.d.ts +1 -0
  3. package/dist/__tests__/contentRouter.test.js +76 -0
  4. package/dist/__tests__/controlEndpointGuard.test.d.ts +1 -0
  5. package/dist/__tests__/controlEndpointGuard.test.js +61 -0
  6. package/dist/__tests__/doctor.test.js +17 -1
  7. package/dist/__tests__/jsonCrush.test.js +59 -1
  8. package/dist/__tests__/outputSavings.test.d.ts +1 -0
  9. package/dist/__tests__/outputSavings.test.js +49 -0
  10. package/dist/__tests__/relevance.test.d.ts +1 -0
  11. package/dist/__tests__/relevance.test.js +66 -0
  12. package/dist/__tests__/secureKeyFile.test.d.ts +1 -0
  13. package/dist/__tests__/secureKeyFile.test.js +27 -0
  14. package/dist/__tests__/serverBinding.test.d.ts +1 -0
  15. package/dist/__tests__/serverBinding.test.js +18 -0
  16. package/dist/__tests__/statsOutput.test.d.ts +1 -0
  17. package/dist/__tests__/statsOutput.test.js +46 -0
  18. package/dist/__tests__/textCrusher.test.js +63 -1
  19. package/dist/codexMitm.d.ts +1 -0
  20. package/dist/codexMitm.js +26 -2
  21. package/dist/compressor.js +31 -3
  22. package/dist/contentRouter.d.ts +30 -0
  23. package/dist/contentRouter.js +112 -0
  24. package/dist/dashboard.d.ts +1 -1
  25. package/dist/dashboard.js +26 -7
  26. package/dist/deterministic.d.ts +1 -1
  27. package/dist/deterministic.js +14 -13
  28. package/dist/doctor.d.ts +5 -0
  29. package/dist/doctor.js +11 -0
  30. package/dist/index.js +4 -1
  31. package/dist/jsonCrush.d.ts +4 -0
  32. package/dist/jsonCrush.js +83 -18
  33. package/dist/outputSavings.d.ts +20 -0
  34. package/dist/outputSavings.js +52 -0
  35. package/dist/relevance.d.ts +27 -0
  36. package/dist/relevance.js +78 -0
  37. package/dist/server.js +84 -11
  38. package/dist/stats.d.ts +17 -0
  39. package/dist/stats.js +55 -0
  40. package/dist/systemPrompt.js +3 -2
  41. package/dist/textCrusher.d.ts +4 -0
  42. package/dist/textCrusher.js +69 -7
  43. package/package.json +69 -69
@@ -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
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_FLASH_MODEL}:generateContent?key=${apiKey}`;
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;
@@ -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
+ }