squeezr-ai 1.17.13 → 1.18.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.
@@ -11,6 +11,10 @@ export interface Savings {
11
11
  }>;
12
12
  dryRun: boolean;
13
13
  sessionCacheHits: number;
14
+ detSavedChars?: number;
15
+ dedupSavedChars?: number;
16
+ aiSavedChars?: number;
17
+ overheadChars?: number;
14
18
  }
15
19
  export declare function getCache(config: Config): CompressionCache;
16
20
  interface AnthropicMessage {
@@ -92,9 +92,11 @@ function buildAndCache(original, result) {
92
92
  const ratio = Math.round((1 - result.length / Math.max(original.length, 1)) * 100);
93
93
  const id = storeOriginal(original);
94
94
  const fullString = `[squeezr:${id} -${ratio}%] ${result}`;
95
- const savedChars = original.length - result.length;
95
+ const overheadChars = fullString.length - result.length; // tag overhead
96
+ // Real savings: original minus what's actually sent (fullString, including tag)
97
+ const savedChars = original.length - fullString.length;
96
98
  setBlock(hashText(original), { fullString, savedChars, originalChars: original.length });
97
- return { fullString, savedChars };
99
+ return { fullString, savedChars, overheadChars };
98
100
  }
99
101
  function extractAnthropicToolResults(messages, toolIdMap) {
100
102
  const results = [];
@@ -152,10 +154,10 @@ export async function compressAnthropicMessages(messages, apiKey, config, system
152
154
  // keep the most recent occurrence at full fidelity and replace earlier ones
153
155
  // with a short reference (saves tokens, model still has access via expand).
154
156
  const dedupedSet = new Set(); // "index:subIndex" keys — skip in later steps
157
+ let readDedupSaved = 0;
155
158
  {
156
159
  const readHashToId = new Map(); // hash → expand id of most recent
157
160
  const seenMostRecent = new Set();
158
- let readDedupSaved = 0;
159
161
  let readDedupCount = 0;
160
162
  // Scan newest → oldest: first encounter of each hash = most recent
161
163
  for (let i = allResults.length - 1; i >= 0; i--) {
@@ -203,11 +205,11 @@ export async function compressAnthropicMessages(messages, apiKey, config, system
203
205
  const candidates = allResults.slice(0, Math.max(0, allResults.length - effectiveKeepRecent(config)));
204
206
  const toProcess = candidates.filter(c => c.text.length >= threshold && !dedupedSet.has(`${c.index}:${c.subIndex}`));
205
207
  if (toProcess.length === 0)
206
- return [msgs, emptySavings()];
208
+ return [msgs, emptySavings(false, detSaved, readDedupSaved)];
207
209
  if (config.dryRun) {
208
210
  const potential = toProcess.reduce((sum, c) => sum + c.text.length, 0);
209
211
  console.log(`[squeezr dry-run] Would AI-compress ${toProcess.length} block(s) | potential -${potential.toLocaleString()} chars | pressure=${Math.round(pressure * 100)}%`);
210
- return [msgs, emptySavings(true)];
212
+ return [msgs, emptySavings(true, detSaved, readDedupSaved)];
211
213
  }
212
214
  // Differential: split session cache hits from uncached
213
215
  const sessionHits = [];
@@ -229,19 +231,24 @@ export async function compressAnthropicMessages(messages, apiKey, config, system
229
231
  : [];
230
232
  let totalOriginal = 0;
231
233
  let totalCompressed = 0;
234
+ let totalOverhead = 0;
235
+ let totalAiSaved = 0;
232
236
  const byTool = [];
233
237
  for (const { index, subIndex, tool, block } of sessionHits) {
234
238
  ;
235
239
  msgs[index].content[subIndex].content = block.fullString;
236
240
  totalOriginal += block.originalChars;
237
241
  totalCompressed += block.originalChars - block.savedChars;
242
+ totalAiSaved += block.savedChars;
238
243
  byTool.push({ tool, savedChars: block.savedChars, originalChars: block.originalChars });
239
244
  }
240
245
  for (const { index, subIndex, original, result, tool } of freshlyCompressed) {
241
- const { fullString, savedChars } = buildAndCache(original, result);
246
+ const { fullString, savedChars, overheadChars } = buildAndCache(original, result);
242
247
  msgs[index].content[subIndex].content = fullString;
243
248
  totalOriginal += original.length;
244
249
  totalCompressed += original.length - savedChars;
250
+ totalOverhead += overheadChars;
251
+ totalAiSaved += savedChars;
245
252
  byTool.push({ tool, savedChars, originalChars: original.length });
246
253
  }
247
254
  if (pressure >= 0.5)
@@ -255,6 +262,10 @@ export async function compressAnthropicMessages(messages, apiKey, config, system
255
262
  byTool,
256
263
  dryRun: false,
257
264
  sessionCacheHits: sessionHits.length,
265
+ detSavedChars: detSaved,
266
+ dedupSavedChars: readDedupSaved,
267
+ aiSavedChars: totalAiSaved,
268
+ overheadChars: totalOverhead,
258
269
  }];
259
270
  }
260
271
  function extractOpenAIToolResults(messages) {
@@ -293,10 +304,11 @@ export async function compressOpenAIMessages(messages, apiKey, config, isLocal =
293
304
  const msgs = structuredClone(messages);
294
305
  // Step 0: Cross-turn Read dedup
295
306
  const dedupedIndices = new Set();
307
+ let readDedupSaved = 0;
296
308
  {
297
309
  const readHashToId = new Map();
298
310
  const seenMostRecent = new Set();
299
- let readDedupSaved = 0, readDedupCount = 0;
311
+ let readDedupCount = 0;
300
312
  for (let i = allResults.length - 1; i >= 0; i--) {
301
313
  const { index, text, tool } = allResults[i];
302
314
  if (tool.toLowerCase() !== 'read')
@@ -337,11 +349,11 @@ export async function compressOpenAIMessages(messages, apiKey, config, isLocal =
337
349
  const candidates = allResults.slice(0, Math.max(0, allResults.length - effectiveKeepRecent(config)));
338
350
  const toProcess = candidates.filter(c => c.text.length >= threshold && !dedupedIndices.has(c.index));
339
351
  if (toProcess.length === 0)
340
- return [msgs, emptySavings()];
352
+ return [msgs, emptySavings(false, detSaved, readDedupSaved)];
341
353
  if (config.dryRun) {
342
354
  const tag = isLocal ? 'ollama' : 'codex';
343
355
  console.log(`[squeezr dry-run/${tag}] Would AI-compress ${toProcess.length} block(s) | potential -${toProcess.reduce((s, c) => s + c.text.length, 0).toLocaleString()} chars`);
344
- return [msgs, emptySavings(true)];
356
+ return [msgs, emptySavings(true, detSaved, readDedupSaved)];
345
357
  }
346
358
  const sessionHits = [];
347
359
  const toCompress = [];
@@ -354,7 +366,6 @@ export async function compressOpenAIMessages(messages, apiKey, config, isLocal =
354
366
  sessionHits.push({ index: c.index, tool: c.tool, block: cached });
355
367
  }
356
368
  else if (aiEnabled() && c.index > newStartIdx && !config.aiSkipTools.has(c.tool.toLowerCase())) {
357
- // Only AI-compress new tool results (after last assistant turn) — prevents burst on first activation.
358
369
  toCompress.push(c);
359
370
  }
360
371
  }
@@ -364,19 +375,22 @@ export async function compressOpenAIMessages(messages, apiKey, config, isLocal =
364
375
  const freshlyCompressed = toCompress.length > 0
365
376
  ? await runCompression(toCompress, compressFn, config)
366
377
  : [];
367
- let totalOriginal = 0, totalCompressed = 0;
378
+ let totalOriginal = 0, totalCompressed = 0, totalOverhead = 0, totalAiSaved = 0;
368
379
  const byTool = [];
369
380
  for (const { index, tool, block } of sessionHits) {
370
381
  msgs[index].content = block.fullString;
371
382
  totalOriginal += block.originalChars;
372
383
  totalCompressed += block.originalChars - block.savedChars;
384
+ totalAiSaved += block.savedChars;
373
385
  byTool.push({ tool, savedChars: block.savedChars, originalChars: block.originalChars });
374
386
  }
375
387
  for (const { index, original, result, tool } of freshlyCompressed) {
376
- const { fullString, savedChars } = buildAndCache(original, result);
388
+ const { fullString, savedChars, overheadChars } = buildAndCache(original, result);
377
389
  msgs[index].content = fullString;
378
390
  totalOriginal += original.length;
379
391
  totalCompressed += original.length - savedChars;
392
+ totalOverhead += overheadChars;
393
+ totalAiSaved += savedChars;
380
394
  byTool.push({ tool, savedChars, originalChars: original.length });
381
395
  }
382
396
  if (pressure >= 0.5) {
@@ -385,7 +399,7 @@ export async function compressOpenAIMessages(messages, apiKey, config, isLocal =
385
399
  }
386
400
  if (sessionHits.length > 0)
387
401
  console.log(`[squeezr] Session cache: ${sessionHits.length} block(s) reused`);
388
- return [msgs, { compressed: freshlyCompressed.length, savedChars: totalOriginal - totalCompressed, originalChars: totalOriginal, byTool, dryRun: false, sessionCacheHits: sessionHits.length }];
402
+ return [msgs, { compressed: freshlyCompressed.length, savedChars: totalOriginal - totalCompressed, originalChars: totalOriginal, byTool, dryRun: false, sessionCacheHits: sessionHits.length, detSavedChars: detSaved, dedupSavedChars: readDedupSaved, aiSavedChars: totalAiSaved, overheadChars: totalOverhead }];
389
403
  }
390
404
  export async function compressGeminiContents(contents, apiKey, config) {
391
405
  if (config.disabled)
@@ -415,10 +429,11 @@ export async function compressGeminiContents(contents, apiKey, config) {
415
429
  const cts = structuredClone(contents);
416
430
  // Step 0: Cross-turn Read dedup
417
431
  const geminiDedupedSet = new Set();
432
+ let geminiReadDedupSaved = 0;
418
433
  {
419
434
  const readHashToId = new Map();
420
435
  const seenMostRecent = new Set();
421
- let readDedupSaved = 0, readDedupCount = 0;
436
+ let readDedupCount = 0;
422
437
  for (let i = allResults.length - 1; i >= 0; i--) {
423
438
  const { index, subIndex, text, tool } = allResults[i];
424
439
  if (tool.toLowerCase() !== 'read')
@@ -432,11 +447,11 @@ export async function compressGeminiContents(contents, apiKey, config) {
432
447
  cts[index].parts[subIndex].functionResponse.response = { output: `[same file content as a later read — squeezr_expand(${readHashToId.get(hash)}) to retrieve]` };
433
448
  geminiDedupedSet.add(`${index}:${subIndex}`);
434
449
  readDedupCount++;
435
- readDedupSaved += text.length;
450
+ geminiReadDedupSaved += text.length;
436
451
  }
437
452
  }
438
- if (readDedupSaved > 0) {
439
- console.log(`[squeezr/read-dedup/gemini] ${readDedupCount} duplicate file read(s) collapsed: -${readDedupSaved.toLocaleString()} chars`);
453
+ if (geminiReadDedupSaved > 0) {
454
+ console.log(`[squeezr/read-dedup/gemini] ${readDedupCount} duplicate file read(s) collapsed: -${geminiReadDedupSaved.toLocaleString()} chars`);
440
455
  hitPattern('readDedup', readDedupCount);
441
456
  }
442
457
  }
@@ -457,10 +472,10 @@ export async function compressGeminiContents(contents, apiKey, config) {
457
472
  const candidates = allResults.slice(0, Math.max(0, allResults.length - effectiveKeepRecent(config)))
458
473
  .filter(c => c.text.length >= threshold && !geminiDedupedSet.has(`${c.index}:${c.subIndex}`));
459
474
  if (candidates.length === 0)
460
- return [cts, emptySavings()];
475
+ return [cts, emptySavings(false, detSaved, geminiReadDedupSaved)];
461
476
  if (config.dryRun) {
462
477
  console.log(`[squeezr dry-run/gemini] Would AI-compress ${candidates.length} block(s) | potential -${candidates.reduce((s, c) => s + c.text.length, 0).toLocaleString()} chars`);
463
- return [cts, emptySavings(true)];
478
+ return [cts, emptySavings(true, detSaved, geminiReadDedupSaved)];
464
479
  }
465
480
  const sessionHits = [];
466
481
  const toCompress = [];
@@ -474,25 +489,28 @@ export async function compressGeminiContents(contents, apiKey, config) {
474
489
  const freshlyCompressed = toCompress.length > 0
475
490
  ? await runCompression(toCompress, t => compressWithGeminiFlash(t, apiKey), config)
476
491
  : [];
477
- let totalOriginal = 0, totalCompressed = 0;
492
+ let totalOriginal = 0, totalCompressed = 0, totalOverhead = 0, totalAiSaved = 0;
478
493
  const byTool = [];
479
494
  for (const { index, subIndex, tool, block } of sessionHits) {
480
495
  cts[index].parts[subIndex].functionResponse.response = { output: block.fullString };
481
496
  totalOriginal += block.originalChars;
482
497
  totalCompressed += block.originalChars - block.savedChars;
498
+ totalAiSaved += block.savedChars;
483
499
  byTool.push({ tool, savedChars: block.savedChars, originalChars: block.originalChars });
484
500
  }
485
501
  for (const { index, subIndex, original, result, tool } of freshlyCompressed) {
486
- const { fullString, savedChars } = buildAndCache(original, result);
502
+ const { fullString, savedChars, overheadChars } = buildAndCache(original, result);
487
503
  cts[index].parts[subIndex].functionResponse.response = { output: fullString };
488
504
  totalOriginal += original.length;
489
505
  totalCompressed += original.length - savedChars;
506
+ totalOverhead += overheadChars;
507
+ totalAiSaved += savedChars;
490
508
  byTool.push({ tool, savedChars, originalChars: original.length });
491
509
  }
492
510
  if (sessionHits.length > 0)
493
511
  console.log(`[squeezr/gemini] Session cache: ${sessionHits.length} block(s) reused`);
494
- return [cts, { compressed: freshlyCompressed.length, savedChars: totalOriginal - totalCompressed, originalChars: totalOriginal, byTool, dryRun: false, sessionCacheHits: sessionHits.length }];
512
+ return [cts, { compressed: freshlyCompressed.length, savedChars: totalOriginal - totalCompressed, originalChars: totalOriginal, byTool, dryRun: false, sessionCacheHits: sessionHits.length, detSavedChars: detSaved, dedupSavedChars: geminiReadDedupSaved, aiSavedChars: totalAiSaved, overheadChars: totalOverhead }];
495
513
  }
496
- function emptySavings(dryRun = false) {
497
- return { compressed: 0, savedChars: 0, originalChars: 0, byTool: [], dryRun, sessionCacheHits: 0 };
514
+ function emptySavings(dryRun = false, detSavedChars = 0, dedupSavedChars = 0) {
515
+ return { compressed: 0, savedChars: 0, originalChars: 0, byTool: [], dryRun, sessionCacheHits: 0, detSavedChars, dedupSavedChars, aiSavedChars: 0, overheadChars: 0 };
498
516
  }