superlocalmemory 3.5.7 → 3.6.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.
Files changed (73) hide show
  1. package/ATTRIBUTION.md +24 -0
  2. package/CHANGELOG.md +40 -0
  3. package/README.md +142 -35
  4. package/package.json +1 -1
  5. package/pyproject.toml +2 -1
  6. package/src/superlocalmemory/__init__.py +1 -1
  7. package/src/superlocalmemory/cli/cache_cmd.py +198 -0
  8. package/src/superlocalmemory/cli/commands.py +100 -2
  9. package/src/superlocalmemory/cli/compress_cmd.py +179 -0
  10. package/src/superlocalmemory/cli/help_cmd.py +197 -0
  11. package/src/superlocalmemory/cli/main.py +122 -0
  12. package/src/superlocalmemory/cli/optimize_cmd.py +175 -0
  13. package/src/superlocalmemory/cli/optimize_constants.py +31 -0
  14. package/src/superlocalmemory/cli/proxy_cmd.py +95 -0
  15. package/src/superlocalmemory/core/config.py +5 -0
  16. package/src/superlocalmemory/core/engine.py +23 -0
  17. package/src/superlocalmemory/core/mcp_embedder_proxy.py +89 -0
  18. package/src/superlocalmemory/infra/process_reaper.py +12 -1
  19. package/src/superlocalmemory/llm/backbone.py +10 -4
  20. package/src/superlocalmemory/mcp/server.py +34 -0
  21. package/src/superlocalmemory/mcp/tools_v3.py +6 -2
  22. package/src/superlocalmemory/optimize/NOTICE +11 -0
  23. package/src/superlocalmemory/optimize/__init__.py +0 -0
  24. package/src/superlocalmemory/optimize/adapters/__init__.py +68 -0
  25. package/src/superlocalmemory/optimize/adapters/_agent_registry.py +120 -0
  26. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +115 -0
  27. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +125 -0
  28. package/src/superlocalmemory/optimize/adapters/wrap.py +188 -0
  29. package/src/superlocalmemory/optimize/cache/__init__.py +31 -0
  30. package/src/superlocalmemory/optimize/cache/boundary_store.py +455 -0
  31. package/src/superlocalmemory/optimize/cache/centroid_store.py +158 -0
  32. package/src/superlocalmemory/optimize/cache/context_key.py +67 -0
  33. package/src/superlocalmemory/optimize/cache/exact.py +85 -0
  34. package/src/superlocalmemory/optimize/cache/invalidation.py +36 -0
  35. package/src/superlocalmemory/optimize/cache/key_builder.py +98 -0
  36. package/src/superlocalmemory/optimize/cache/manager.py +452 -0
  37. package/src/superlocalmemory/optimize/cache/semantic.py +568 -0
  38. package/src/superlocalmemory/optimize/cache/stampede.py +50 -0
  39. package/src/superlocalmemory/optimize/compress/__init__.py +17 -0
  40. package/src/superlocalmemory/optimize/compress/align.py +153 -0
  41. package/src/superlocalmemory/optimize/compress/ccr.py +157 -0
  42. package/src/superlocalmemory/optimize/compress/extractive_code.py +311 -0
  43. package/src/superlocalmemory/optimize/compress/extractive_json.py +72 -0
  44. package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +77 -0
  45. package/src/superlocalmemory/optimize/compress/router.py +548 -0
  46. package/src/superlocalmemory/optimize/config/__init__.py +35 -0
  47. package/src/superlocalmemory/optimize/config/defaults.py +48 -0
  48. package/src/superlocalmemory/optimize/config/schema.py +255 -0
  49. package/src/superlocalmemory/optimize/config/store.py +209 -0
  50. package/src/superlocalmemory/optimize/metrics/__init__.py +8 -0
  51. package/src/superlocalmemory/optimize/metrics/counters.py +138 -0
  52. package/src/superlocalmemory/optimize/metrics/estimator.py +90 -0
  53. package/src/superlocalmemory/optimize/metrics/exporters.py +77 -0
  54. package/src/superlocalmemory/optimize/metrics/persistence.py +115 -0
  55. package/src/superlocalmemory/optimize/proxy/__init__.py +28 -0
  56. package/src/superlocalmemory/optimize/proxy/_helpers.py +257 -0
  57. package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +171 -0
  58. package/src/superlocalmemory/optimize/proxy/gemini_surface.py +121 -0
  59. package/src/superlocalmemory/optimize/proxy/lifecycle.py +126 -0
  60. package/src/superlocalmemory/optimize/proxy/openai_surface.py +125 -0
  61. package/src/superlocalmemory/optimize/proxy/server.py +151 -0
  62. package/src/superlocalmemory/optimize/storage/__init__.py +0 -0
  63. package/src/superlocalmemory/optimize/storage/db.py +1016 -0
  64. package/src/superlocalmemory/optimize/storage/schema.py +184 -0
  65. package/src/superlocalmemory/server/routes/optimize.py +166 -0
  66. package/src/superlocalmemory/server/routes/v3_api.py +63 -1
  67. package/src/superlocalmemory/server/unified_daemon.py +105 -0
  68. package/src/superlocalmemory/ui/index.html +98 -0
  69. package/src/superlocalmemory/ui/js/ng-shell.js +2 -1
  70. package/src/superlocalmemory/ui/js/optimize.js +173 -0
  71. package/src/superlocalmemory.egg-info/PKG-INFO +2 -1
  72. package/src/superlocalmemory.egg-info/SOURCES.txt +51 -0
  73. package/src/superlocalmemory.egg-info/requires.txt +1 -0
@@ -0,0 +1,77 @@
1
+ # compress/prose_llmlingua.py
2
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
3
+ # Licensed under AGPL-3.0-or-later
4
+ #
5
+ # LLMLingua-2 paper: Microsoft Research + MIT (2024).
6
+ # Model: XLM-RoBERTa fine-tuned as token binary classifier.
7
+ # License: MIT (github.com/microsoft/LLMLingua)
8
+
9
+ """LLMLinguaCompressor — opt-in LLMLingua-2 prose compressor.
10
+
11
+ SAFETY RULES (NON-NEGOTIABLE):
12
+ 1. ONLY called for prose/narrative content — NEVER JSON or code.
13
+ 2. compress_mode MUST be "aggressive" in optimize.json.
14
+ 3. Lossy — CCR stores original before this runs.
15
+ 4. Import errors caught — returns original on failure.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from typing import Any
22
+
23
+ logger = logging.getLogger("slm.optimize.compress.llmlingua")
24
+
25
+ _DEFAULT_RATE: float = 0.5
26
+ _MODEL_BERT: str = "microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank"
27
+ _MODEL_XLM: str = "microsoft/llmlingua-2-xlm-roberta-large-meetingbank"
28
+
29
+
30
+ class LLMLinguaCompressor:
31
+ """Opt-in LLMLingua-2 prose compressor."""
32
+
33
+ def __init__(
34
+ self,
35
+ model_name: str = _MODEL_BERT,
36
+ device_map: str = "cpu",
37
+ rate: float = _DEFAULT_RATE,
38
+ ) -> None:
39
+ import os
40
+ if os.environ.get("SLM_DISABLE_HF_DOWNLOAD", "0") == "1":
41
+ raise ImportError(
42
+ "LLMLingua-2 model download blocked: SLM_DISABLE_HF_DOWNLOAD=1. "
43
+ "Set compress_llmlingua_allow_download=true in optimize.json."
44
+ )
45
+ try:
46
+ from llmlingua import PromptCompressor # type: ignore[import]
47
+ except ImportError as e:
48
+ raise ImportError(
49
+ "llmlingua package not installed. Install: pip install llmlingua."
50
+ ) from e
51
+
52
+ logger.info("Loading LLMLingua-2 model=%s device=%s", model_name, device_map)
53
+ self._compressor: Any = PromptCompressor(
54
+ model_name=model_name,
55
+ use_llmlingua2=True,
56
+ device_map=device_map,
57
+ )
58
+ self._rate = rate
59
+ logger.info("LLMLingua-2 loaded successfully model=%s", model_name)
60
+
61
+ def compress(self, text: str, rate: float | None = None) -> str:
62
+ """Compress prose text using LLMLingua-2. Fail-open: returns original on error."""
63
+ effective_rate = rate if rate is not None else self._rate
64
+ try:
65
+ result = self._compressor.compress_prompt(
66
+ [text],
67
+ rate=effective_rate,
68
+ use_token_level_filter=True,
69
+ )
70
+ compressed = result.get("compressed_prompt", text)
71
+ if not isinstance(compressed, str) or not compressed:
72
+ logger.warning("LLMLingua-2 returned unexpected output — passthrough")
73
+ return text
74
+ return compressed
75
+ except Exception as exc:
76
+ logger.warning("LLMLingua-2 compress failed — passthrough: %s", exc)
77
+ return text
@@ -0,0 +1,548 @@
1
+ # compress/router.py
2
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
3
+ # Licensed under AGPL-3.0-or-later
4
+ #
5
+ # Routing pattern adapted from:
6
+ # headroom/transforms/content_router.py (Apache-2.0, Headroom contributors)
7
+ # Specifically: ContentRouter._determine_strategy(), _strategy_from_detection()
8
+ # Attribution: See ATTRIBUTION.md.
9
+
10
+ """CompressRouter — implements CompressHook, dispatches to sub-compressors.
11
+
12
+ INTERFACE-CONTRACT v2.2 §3 defines: compress(ProxyRequest) → ProxyRequest.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import logging
19
+ import threading
20
+ from dataclasses import dataclass
21
+ from typing import Any
22
+
23
+ from superlocalmemory.optimize.proxy.lifecycle import ProxyRequest, CompressHook
24
+ from superlocalmemory.optimize.config.store import ConfigStore
25
+
26
+ logger = logging.getLogger("slm.optimize.compress.router")
27
+
28
+ _MIN_CHARS_FOR_COMPRESSION: int = 500
29
+ _MIN_RATIO_STRUCTURED: float = 0.60
30
+
31
+
32
+ class CompressRouter:
33
+ """Implements CompressHook Protocol per INTERFACE-CONTRACT §3.
34
+
35
+ compress(req: ProxyRequest) → ProxyRequest
36
+ on_compress(before_tokens, after_tokens, lossy) — metrics callback
37
+
38
+ Singleton per daemon instance.
39
+ """
40
+
41
+ _instance: "CompressRouter | None" = None
42
+ _lock: threading.Lock = threading.Lock()
43
+
44
+ @classmethod
45
+ def get_instance(cls) -> "CompressRouter":
46
+ if cls._instance is None:
47
+ with cls._lock:
48
+ if cls._instance is None:
49
+ cls._instance = cls()
50
+ return cls._instance
51
+
52
+ def __init__(self) -> None:
53
+ self._json_compressor: "JSONCompressor | None" = None
54
+ self._code_compressor: "CodeCompressor | None" = None
55
+ self._llmlingua_compressor: "LLMLinguaCompressor | None" = None
56
+ self._ccr_store: "CCRStore | None" = None
57
+ self._aligner: "CacheAligner | None" = None
58
+ self._config_store: ConfigStore | None = None
59
+ self._metrics_counters: Any = None
60
+
61
+ # ── CompressHook Protocol (INTERFACE-CONTRACT §3) ──────────────────────
62
+
63
+ def compress(self, req: ProxyRequest) -> ProxyRequest:
64
+ """Compress request body. Called by proxy after cache miss.
65
+
66
+ CONTRACT §3: compress(req: ProxyRequest) -> ProxyRequest (NOT ctx → CompressResult).
67
+ Returns req unchanged on error / no-op (fail-open).
68
+ """
69
+ try:
70
+ cfg = self._get_config()
71
+
72
+ if not cfg.compress_enabled:
73
+ return req
74
+ if req.stream:
75
+ return req
76
+ if req.has_tools:
77
+ return req # §6.5 safety rule
78
+
79
+ body = dict(req.body)
80
+ messages = body.get("messages", [])
81
+ if not isinstance(messages, list):
82
+ return req
83
+
84
+ # Step 5: CacheAligner on system prompt (detection only)
85
+ system_text = body.get("system", "")
86
+ if isinstance(system_text, str) and system_text:
87
+ aligner = self._get_aligner()
88
+ align_result = aligner.detect(system_text)
89
+ if align_result.findings:
90
+ logger.warning(
91
+ "[%s] CacheAligner: %d volatile tokens — "
92
+ "provider prefix cache may be unstable: %s",
93
+ req.request_id,
94
+ len(align_result.findings),
95
+ [f.label for f in align_result.findings[:5]],
96
+ )
97
+
98
+ # Step 6: compress each content block
99
+ aggressive = cfg.compress_mode == "aggressive"
100
+ protect_recent = cfg.compress_protect_recent
101
+ new_messages, tokens_before, tokens_after, strategy = self._compress_messages(
102
+ messages=messages,
103
+ aggressive=aggressive,
104
+ protect_recent=protect_recent,
105
+ request_id=req.request_id,
106
+ model=body.get("model", ""),
107
+ tenant_id="default",
108
+ )
109
+
110
+ if tokens_after >= tokens_before:
111
+ return req # no improvement
112
+
113
+ body["messages"] = new_messages
114
+ new_bytes = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode()
115
+
116
+ # CONTRACT §3: fire on_compress metrics callback
117
+ lossy = strategy == "llmlingua2_prose"
118
+ self.on_compress(tokens_before, tokens_after, lossy)
119
+
120
+ return ProxyRequest(
121
+ provider=req.provider,
122
+ method=req.method,
123
+ path=req.path,
124
+ headers=req.headers,
125
+ body=body,
126
+ body_bytes=new_bytes,
127
+ request_id=req.request_id,
128
+ stream=req.stream,
129
+ has_tools=req.has_tools,
130
+ )
131
+
132
+ except Exception as exc:
133
+ logger.warning("[%s] CompressRouter.compress failed (fail-open): %s",
134
+ req.request_id if hasattr(req, 'request_id') else '?', exc)
135
+ return req
136
+
137
+ def on_compress(self, before_tokens: int, after_tokens: int, lossy: bool) -> None:
138
+ """Metrics callback — CONTRACT §3. MUST NOT raise."""
139
+ try:
140
+ saved = max(0, before_tokens - after_tokens)
141
+ if self._metrics_counters is not None:
142
+ # M-02: Call proper method instead of accessing private attribute
143
+ self._metrics_counters.on_compress(saved, lossy)
144
+ logger.debug("on_compress: saved=%d tokens lossy=%s", saved, lossy)
145
+ except Exception as exc:
146
+ logger.debug("on_compress metrics update failed (non-fatal): %s", exc)
147
+
148
+ def set_metrics(self, counters: Any) -> None:
149
+ self._metrics_counters = counters
150
+
151
+ # ── Internal routing ──────────────────────────────────────────────────
152
+
153
+ def _compress_messages(
154
+ self,
155
+ messages: list[dict[str, Any]],
156
+ aggressive: bool,
157
+ protect_recent: int,
158
+ request_id: str,
159
+ model: str,
160
+ tenant_id: str,
161
+ ) -> tuple[list[dict[str, Any]], int, int, str]:
162
+ total_before = 0
163
+ total_after = 0
164
+ primary_strategy = "none"
165
+ new_messages: list[dict[str, Any]] = []
166
+
167
+ protect_indices = set(range(max(0, len(messages) - protect_recent), len(messages)))
168
+
169
+ for idx, msg in enumerate(messages):
170
+ role = msg.get("role", "")
171
+ is_tool_msg = (
172
+ role == "tool"
173
+ or role == "user"
174
+ or _msg_has_tool_result(msg)
175
+ )
176
+ if idx in protect_indices or is_tool_msg:
177
+ new_messages.append(msg)
178
+ continue
179
+
180
+ new_content, before, after, strat = self._compress_content_block(
181
+ content=msg.get("content", ""),
182
+ aggressive=aggressive,
183
+ request_id=request_id,
184
+ model=model,
185
+ tenant_id=tenant_id,
186
+ )
187
+ total_before += before
188
+ total_after += after
189
+ if strat != "none":
190
+ primary_strategy = strat
191
+
192
+ new_msg = dict(msg)
193
+ new_msg["content"] = new_content
194
+ new_messages.append(new_msg)
195
+
196
+ return new_messages, total_before, total_after, primary_strategy
197
+
198
+ def _compress_content_block(
199
+ self,
200
+ content: Any,
201
+ aggressive: bool,
202
+ request_id: str,
203
+ model: str,
204
+ tenant_id: str,
205
+ ) -> tuple[Any, int, int, str]:
206
+ if isinstance(content, str):
207
+ return self._compress_text(content, aggressive, request_id, model, tenant_id)
208
+
209
+ if isinstance(content, list):
210
+ new_blocks: list[Any] = []
211
+ total_before = 0
212
+ total_after = 0
213
+ primary = "none"
214
+ for block in content:
215
+ if not isinstance(block, dict):
216
+ new_blocks.append(block)
217
+ continue
218
+ block_type = block.get("type", "")
219
+ if block_type in ("text", "tool_result"):
220
+ text = block.get("text", "") or _tool_result_text(block)
221
+ if len(text) < _MIN_CHARS_FOR_COMPRESSION:
222
+ new_blocks.append(block)
223
+ continue
224
+ new_text, before, after, strat = self._compress_text(
225
+ text, aggressive, request_id, model, tenant_id
226
+ )
227
+ total_before += before
228
+ total_after += after
229
+ if strat != "none":
230
+ primary = strat
231
+ new_block = dict(block)
232
+ if block_type == "text":
233
+ new_block["text"] = new_text
234
+ else:
235
+ new_block = _set_tool_result_text(new_block, new_text)
236
+ new_blocks.append(new_block)
237
+ else:
238
+ new_blocks.append(block)
239
+ return new_blocks, total_before, total_after, primary
240
+
241
+ return content, 0, 0, "none"
242
+
243
+ def _compress_text(
244
+ self,
245
+ text: str,
246
+ aggressive: bool,
247
+ request_id: str,
248
+ model: str,
249
+ tenant_id: str,
250
+ ) -> tuple[str, int, int, str]:
251
+ tokens_before = _token_estimate(text)
252
+
253
+ # JSON detection
254
+ stripped = text.strip()
255
+ if stripped.startswith(("{", "[")):
256
+ try:
257
+ parsed = json.loads(stripped)
258
+ compressor = self._get_json_compressor()
259
+ compressed = compressor.compress(parsed)
260
+ tokens_after = _token_estimate_structured(compressed)
261
+ tokens_before_adj = _token_estimate_structured(text)
262
+ ratio = tokens_after / tokens_before_adj if tokens_before_adj else 1.0
263
+ if ratio < _MIN_RATIO_STRUCTURED:
264
+ # B-03: store-before-compress
265
+ ccr_id = self._ccr_store_original(text.encode(), model, tenant_id)
266
+ if ccr_id:
267
+ try:
268
+ obj = json.loads(compressed)
269
+ if isinstance(obj, dict):
270
+ obj["__slm_ccr__"] = ccr_id
271
+ compressed = json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
272
+ elif isinstance(obj, list):
273
+ # RB-02: list-root embedding
274
+ wrapper = {"__slm_ccr__": ccr_id, "__slm_data__": obj}
275
+ compressed = json.dumps(wrapper, ensure_ascii=False, separators=(",", ":"))
276
+ except Exception:
277
+ pass
278
+ self._ccr_update_compressed(ccr_id, compressed.encode())
279
+ logger.debug("[%s] JSON compressed %.2f ratio ccr_id=%s", request_id, ratio, ccr_id)
280
+ return compressed, tokens_before_adj, tokens_after, "extractive_json"
281
+ return text, tokens_before, tokens_before, "none"
282
+ except (json.JSONDecodeError, Exception):
283
+ pass
284
+
285
+ # Code detection
286
+ lang = _detect_language(text)
287
+ if lang is not None:
288
+ compressor = self._get_code_compressor()
289
+ # RB-03: compress first, compute ratio, then store CCR only if beneficial
290
+ compressed_probe = compressor.compress(text, language=lang, ccr_id="")
291
+ tokens_after = _token_estimate(compressed_probe)
292
+ ratio = tokens_after / tokens_before if tokens_before else 1.0
293
+ if ratio < _MIN_RATIO_STRUCTURED:
294
+ # B-03: store-before-compress
295
+ ccr_id = self._ccr_store_original(text.encode(), model, tenant_id)
296
+ compressed = compressor.compress(text, language=lang, ccr_id=ccr_id)
297
+ if ccr_id:
298
+ self._ccr_update_compressed(ccr_id, compressed.encode())
299
+ logger.debug("[%s] Code compressed lang=%s ratio=%.2f ccr_id=%s",
300
+ request_id, lang, ratio, ccr_id)
301
+ return compressed, tokens_before, tokens_after, "extractive_code"
302
+ return text, tokens_before, tokens_before, "none"
303
+
304
+ # Prose: only if aggressive mode AND compress_prose is enabled
305
+ # (Phase 3 — opt-in prose tier; off by default; gated by config
306
+ # field compress_prose added in LLD-04 INTERFACE-CONTRACT v2.)
307
+ cfg = self._get_config()
308
+ prose_enabled = bool(getattr(cfg, "compress_prose", False))
309
+ if aggressive and prose_enabled:
310
+ compressor = self._get_llmlingua_compressor()
311
+ if compressor is not None:
312
+ # B-03: store-before-compress
313
+ ccr_id = self._ccr_store_original(text.encode(), model, tenant_id)
314
+ compressed = compressor.compress(text)
315
+ if ccr_id:
316
+ self._ccr_update_compressed(ccr_id, compressed.encode())
317
+ tokens_after = _token_estimate(compressed)
318
+ logger.info(
319
+ "[%s] LLMLingua-2 prose compressed rate=%.2f ccr_id=%s (LOSSY)",
320
+ request_id,
321
+ tokens_after / tokens_before if tokens_before else 1.0,
322
+ ccr_id,
323
+ )
324
+ return compressed, tokens_before, tokens_after, "llmlingua2_prose"
325
+
326
+ return text, tokens_before, tokens_before, "none"
327
+
328
+ # ── Lazy loaders ─────────────────────────────────────────────────────
329
+
330
+ def _get_json_compressor(self) -> "JSONCompressor":
331
+ if self._json_compressor is None:
332
+ from superlocalmemory.optimize.compress.extractive_json import JSONCompressor
333
+ self._json_compressor = JSONCompressor()
334
+ return self._json_compressor
335
+
336
+ def _get_code_compressor(self) -> "CodeCompressor":
337
+ if self._code_compressor is None:
338
+ from superlocalmemory.optimize.compress.extractive_code import CodeCompressor
339
+ self._code_compressor = CodeCompressor()
340
+ return self._code_compressor
341
+
342
+ def _get_llmlingua_compressor(self) -> "LLMLinguaCompressor | None":
343
+ if self._llmlingua_compressor is None:
344
+ try:
345
+ from superlocalmemory.optimize.compress.prose_llmlingua import LLMLinguaCompressor
346
+ self._llmlingua_compressor = LLMLinguaCompressor()
347
+ except ImportError:
348
+ logger.warning("LLMLinguaCompressor not available — prose compression disabled")
349
+ return None
350
+ return self._llmlingua_compressor
351
+
352
+ def _get_ccr_store(self) -> "CCRStore":
353
+ if self._ccr_store is None:
354
+ from superlocalmemory.optimize.compress.ccr import CCRStore
355
+ self._ccr_store = CCRStore()
356
+ return self._ccr_store
357
+
358
+ def _get_aligner(self) -> "CacheAligner":
359
+ if self._aligner is None:
360
+ from superlocalmemory.optimize.compress.align import CacheAligner
361
+ self._aligner = CacheAligner()
362
+ return self._aligner
363
+
364
+ def _get_config(self):
365
+ if self._config_store is None:
366
+ self._config_store = ConfigStore()
367
+ return self._config_store.get()
368
+
369
+ # ── CCR helpers ───────────────────────────────────────────────────────
370
+
371
+ def _ccr_store_original(self, original_bytes: bytes, model: str, tenant_id: str) -> str:
372
+ """B-03: Store original BEFORE compression. Returns ccr_id or '' on failure."""
373
+ try:
374
+ store = self._get_ccr_store()
375
+ return store.store(original=original_bytes, model=model, tenant_id=tenant_id)
376
+ except Exception as exc:
377
+ logger.warning("CCR store failed (non-fatal): %s", exc)
378
+ return ""
379
+
380
+ def _ccr_update_compressed(self, ccr_id: str, compressed_bytes: bytes) -> None:
381
+ """B-03: Update CCR row with compressed bytes after compression completes."""
382
+ try:
383
+ store = self._get_ccr_store()
384
+ store.update_compressed(ccr_id, compressed_bytes)
385
+ except Exception as exc:
386
+ logger.debug("CCR update_compressed failed (non-fatal): %s", exc)
387
+
388
+ # ── Public convenience method (M-06) ──────────────────────────────────
389
+
390
+ def compress_text(self, text: str, strategy: str = "auto") -> "CompressTextResult":
391
+ """Convenience method for test harness. NEVER raises."""
392
+ try:
393
+ cfg = self._get_config()
394
+ aggressive = cfg.compress_mode == "aggressive"
395
+ compressed, tb, ta, strat = self._compress_text(
396
+ text, aggressive, request_id="eval", model="", tenant_id="default"
397
+ )
398
+ return CompressTextResult(
399
+ compressed_text=compressed, strategy=strat,
400
+ tokens_before=tb, tokens_after=ta,
401
+ )
402
+ except Exception as exc:
403
+ logger.debug("compress_text failed (non-fatal): %s", exc)
404
+ return CompressTextResult(
405
+ compressed_text=text, strategy="none",
406
+ tokens_before=len(text.split()), tokens_after=len(text.split()),
407
+ )
408
+
409
+
410
+ @dataclass
411
+ class CompressTextResult:
412
+ compressed_text: str
413
+ strategy: str # "extractive_json" | "extractive_code" | "llmlingua2_prose" | "none"
414
+ tokens_before: int
415
+ tokens_after: int
416
+
417
+
418
+ # ── Module-level helpers ──────────────────────────────────────────────────────
419
+
420
+ def _token_estimate(text: str) -> int:
421
+ return len(text.split()) if text else 0
422
+
423
+
424
+ def _token_estimate_structured(text: str) -> int:
425
+ return max(1, len(text) // 4) if text else 0
426
+
427
+
428
+ def _msg_has_tool_result(msg: dict) -> bool:
429
+ """B-09: Detect historical tool_result blocks in messages."""
430
+ content = msg.get("content", "")
431
+ if isinstance(content, list):
432
+ for block in content:
433
+ if not isinstance(block, dict):
434
+ continue
435
+ if block.get("type") == "tool_result":
436
+ return True
437
+ if "tool_use_id" in block:
438
+ return True
439
+ return False
440
+
441
+
442
+ def _detect_language(text: str) -> str | None:
443
+ """Detect programming language. Returns None if ambiguous.
444
+
445
+ B-11: Line-start anchored regex. Threshold = 3 signal lines.
446
+ """
447
+ import re
448
+
449
+ if not text or len(text) < 50:
450
+ return None
451
+
452
+ lines = text.split("\n", 30)
453
+
454
+ if lines and lines[0].startswith("#!"):
455
+ shebang = lines[0].lower()
456
+ if "python" in shebang:
457
+ return "python"
458
+ if "node" in shebang or "javascript" in shebang:
459
+ return "javascript"
460
+
461
+ if lines and lines[0].startswith("```"):
462
+ lang_hint = lines[0][3:].strip().lower()
463
+ _KNOWN = {"python", "javascript", "js", "typescript", "ts", "go", "rust", "java", "cpp", "c++", "c"}
464
+ if lang_hint in _KNOWN:
465
+ if lang_hint in ("cpp", "c++"):
466
+ return "cpp"
467
+ if lang_hint in ("js", "typescript", "ts"):
468
+ return "javascript"
469
+ return lang_hint
470
+
471
+ _LANG_PATTERNS: dict[str, list[str]] = {
472
+ "python": [
473
+ r"^\s*(async\s+)?def\s+\w+\s*\(",
474
+ r"^\s*class\s+\w+[\s:(]",
475
+ r"^\s*import\s+\w+",
476
+ r"^\s*from\s+\w+\s+import\s+",
477
+ ],
478
+ "javascript": [
479
+ r"^\s*(async\s+)?function\s+\w+\s*\(",
480
+ r"^\s*class\s+\w+(\s+extends)?",
481
+ r"^\s*const\s+\w+\s*=",
482
+ r"^\s*import\s+.+from\s+['\"]",
483
+ r"^\s*export\s+(default\s+|const\s+|class\s+)",
484
+ ],
485
+ "go": [
486
+ r"^\s*func\s+(\(\w[\w\s\*]*\)\s+)?\w+\s*\(",
487
+ r"^\s*package\s+\w+",
488
+ r"^\s*import\s+[\(\"\`]",
489
+ r"^\s*type\s+\w+\s+(struct|interface)\s*\{",
490
+ ],
491
+ "rust": [
492
+ r"^\s*(pub\s+)?(async\s+)?fn\s+\w+",
493
+ r"^\s*use\s+\w+",
494
+ r"^\s*(pub\s+)?struct\s+\w+",
495
+ r"^\s*impl(\s+\w+)?\s+",
496
+ ],
497
+ "java": [
498
+ r"^\s*(public|private|protected)\s+(static\s+)?\w+\s+\w+\s*\(",
499
+ r"^\s*import\s+[\w\.]+;",
500
+ r"^\s*(public|private|protected)?\s*class\s+\w+",
501
+ ],
502
+ }
503
+
504
+ def _count_signals(patterns: list[str]) -> int:
505
+ count = 0
506
+ for line in lines:
507
+ for pat in patterns:
508
+ if re.search(pat, line):
509
+ count += 1
510
+ break
511
+ return count
512
+
513
+ scores: dict[str, int] = {
514
+ lang: _count_signals(patterns)
515
+ for lang, patterns in _LANG_PATTERNS.items()
516
+ }
517
+ best_lang, best_score = max(scores.items(), key=lambda kv: kv[1])
518
+ if best_score >= 3:
519
+ return best_lang
520
+
521
+ return None
522
+
523
+
524
+ def _tool_result_text(block: dict) -> str:
525
+ content = block.get("content", "")
526
+ if isinstance(content, str):
527
+ return content
528
+ if isinstance(content, list):
529
+ parts = [b.get("text", "") for b in content if isinstance(b, dict) and b.get("type") == "text"]
530
+ return "\n".join(parts)
531
+ return ""
532
+
533
+
534
+ def _set_tool_result_text(block: dict, new_text: str) -> dict:
535
+ content = block.get("content", "")
536
+ if isinstance(content, str):
537
+ return {**block, "content": new_text}
538
+ if isinstance(content, list):
539
+ new_content = []
540
+ replaced = False
541
+ for b in content:
542
+ if not replaced and isinstance(b, dict) and b.get("type") == "text":
543
+ new_content.append({**b, "text": new_text})
544
+ replaced = True
545
+ else:
546
+ new_content.append(b)
547
+ return {**block, "content": new_content}
548
+ return block
@@ -0,0 +1,35 @@
1
+ """Module-level accessor for OptimizeConfig (INTERFACE-CONTRACT §2).
2
+
3
+ LLD-01 (and all other Optimize LLDs) import get_optimize_config() from this
4
+ module. They NEVER construct ConfigStore themselves or read optimize.json directly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from superlocalmemory.optimize.config.schema import OptimizeConfig
10
+
11
+ _store: "ConfigStore | None" = None
12
+
13
+
14
+ def get_optimize_config() -> OptimizeConfig:
15
+ """Return the current active OptimizeConfig (thread-safe, no I/O)."""
16
+ if _store is None:
17
+ from superlocalmemory.optimize.config.defaults import DEFAULT_OPTIMIZE_CONFIG
18
+ return DEFAULT_OPTIMIZE_CONFIG
19
+ return _store.get()
20
+
21
+
22
+ def load_optimize_config() -> OptimizeConfig:
23
+ """Alias of get_optimize_config() — INTERFACE-CONTRACT v2 §2."""
24
+ return get_optimize_config()
25
+
26
+
27
+ def _set_config_store(store: "ConfigStore") -> None:
28
+ global _store
29
+ _store = store
30
+
31
+
32
+ def _reset_config_store() -> None:
33
+ """Reset the module-level store (testing only)."""
34
+ global _store
35
+ _store = None