xithead 1.1.0__py3-none-any.whl

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.
xithead/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ """xithead - Claude Context Optimizer by Xtream-ITSolutions UG."""
2
+
3
+ from xithead._version import __version__
4
+ from xithead.claude_optimizer import ClaudeOptimizer, ClaudeOptimizerConfig, optimize_for_claude
5
+ from xithead.cache import XitheadCache
6
+
7
+ __all__ = [
8
+ "__version__",
9
+ "ClaudeOptimizer",
10
+ "ClaudeOptimizerConfig",
11
+ "optimize_for_claude",
12
+ "XitheadCache",
13
+ ]
14
+
15
+ # Lazy import headroom symbols if available
16
+ try:
17
+ from headroom import compress, HeadroomClient
18
+ __all__ += ["compress", "HeadroomClient"]
19
+ except ImportError:
20
+ pass
21
+
22
+
23
+ def start_proxy(host: str = "127.0.0.1", port: int = 8787, model: str = "claude-sonnet-4-6") -> None:
24
+ """Start the xithead optimization proxy. Convenience function."""
25
+ import os
26
+ os.environ.setdefault("XITHEAD_MODEL", model)
27
+ from xithead.proxy import create_app
28
+ import uvicorn
29
+ app = create_app()
30
+ print(f"[xithead] Proxy on http://{host}:{port} | Model: {model}")
31
+ print(f"[xithead] Dashboard: http://{host}:{port}/dashboard")
32
+ uvicorn.run(app, host=host, port=port)
xithead/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "1.1.0"
xithead/cache.py ADDED
@@ -0,0 +1,260 @@
1
+ """XitheadCache - Multi-layer caching for maximum Claude token savings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import sqlite3
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ _SCHEMA = """
14
+ CREATE TABLE IF NOT EXISTS cache (
15
+ key TEXT PRIMARY KEY,
16
+ value TEXT NOT NULL,
17
+ created_at REAL NOT NULL,
18
+ last_hit REAL,
19
+ hits INTEGER DEFAULT 0,
20
+ ttl REAL NOT NULL DEFAULT 604800
21
+ );
22
+ CREATE INDEX IF NOT EXISTS idx_created ON cache(created_at);
23
+ CREATE INDEX IF NOT EXISTS idx_last_hit ON cache(last_hit);
24
+
25
+ CREATE TABLE IF NOT EXISTS stats (
26
+ id INTEGER PRIMARY KEY CHECK (id = 1),
27
+ total_sets INTEGER DEFAULT 0,
28
+ total_gets INTEGER DEFAULT 0,
29
+ total_hits INTEGER DEFAULT 0,
30
+ total_misses INTEGER DEFAULT 0,
31
+ tokens_saved INTEGER DEFAULT 0,
32
+ cost_saved_usd REAL DEFAULT 0.0
33
+ );
34
+ INSERT OR IGNORE INTO stats (id) VALUES (1);
35
+ """
36
+
37
+
38
+ class XitheadCache:
39
+ """Multi-layer cache: memory → SQLite.
40
+
41
+ Layer 1: In-memory LRU (instant, survives within process)
42
+ Layer 2: SQLite (persistent across restarts, ~100µs per lookup)
43
+
44
+ Features:
45
+ - Per-entry TTL (frequently-hit entries get longer TTL automatically)
46
+ - Hit-rate tracking across restarts
47
+ - LRU eviction in memory layer
48
+ - Automatic DB vacuum when size exceeds limit
49
+ """
50
+
51
+ DEFAULT_TTL = 86400 * 7 # 7 days baseline
52
+ HOT_TTL = 86400 * 30 # 30 days for frequently-hit entries (≥5 hits)
53
+ WARM_TTL = 86400 * 14 # 14 days for entries with ≥2 hits
54
+
55
+ def __init__(
56
+ self,
57
+ cache_dir: Path | None = None,
58
+ max_memory_entries: int = 2000,
59
+ ttl_seconds: int | None = None,
60
+ max_db_size_mb: int = 500,
61
+ ) -> None:
62
+ self._dir = cache_dir or (Path.home() / ".xithead" / "cache")
63
+ self._dir.mkdir(parents=True, exist_ok=True)
64
+ self._db_path = self._dir / "xithead_cache.db"
65
+ self._memory: dict[str, tuple[float, float, Any]] = {} # key → (ts, expiry, value)
66
+ self._access_order: list[str] = [] # LRU tracking
67
+ self._max_memory = max_memory_entries
68
+ self._default_ttl = ttl_seconds or self.DEFAULT_TTL
69
+ self._max_db_mb = max_db_size_mb
70
+ self._init_db()
71
+ self._maybe_vacuum()
72
+
73
+ def _init_db(self) -> None:
74
+ with sqlite3.connect(self._db_path) as conn:
75
+ conn.executescript(_SCHEMA)
76
+ conn.commit()
77
+
78
+ def _maybe_vacuum(self) -> None:
79
+ """Clean up expired entries and vacuum if DB is too large."""
80
+ try:
81
+ with sqlite3.connect(self._db_path) as conn:
82
+ now = time.time()
83
+ # Delete expired entries
84
+ conn.execute("DELETE FROM cache WHERE created_at + ttl < ?", (now,))
85
+ conn.commit()
86
+ size_mb = self._db_path.stat().st_size / 1024 / 1024
87
+ if size_mb > self._max_db_mb:
88
+ # Delete least recently hit entries until under limit
89
+ conn.execute(
90
+ "DELETE FROM cache WHERE key IN ("
91
+ " SELECT key FROM cache ORDER BY COALESCE(last_hit, created_at) ASC"
92
+ " LIMIT (SELECT COUNT(*)/4 FROM cache)"
93
+ ")"
94
+ )
95
+ conn.execute("VACUUM")
96
+ conn.commit()
97
+ except Exception:
98
+ pass
99
+
100
+ # ------------------------------------------------------------------
101
+ # Key generation
102
+ # ------------------------------------------------------------------
103
+
104
+ def key(self, *parts: Any) -> str:
105
+ payload = json.dumps(parts, sort_keys=True, default=str)
106
+ return hashlib.sha256(payload.encode()).hexdigest()
107
+
108
+ # ------------------------------------------------------------------
109
+ # Get / Set
110
+ # ------------------------------------------------------------------
111
+
112
+ def get(self, key: str) -> Any | None:
113
+ now = time.time()
114
+
115
+ # L1: memory
116
+ if key in self._memory:
117
+ ts, expiry, val = self._memory[key]
118
+ if now < expiry:
119
+ self._lru_touch(key)
120
+ return val
121
+ del self._memory[key]
122
+
123
+ # L2: SQLite
124
+ try:
125
+ with sqlite3.connect(self._db_path) as conn:
126
+ row = conn.execute(
127
+ "SELECT value, created_at, ttl, hits FROM cache WHERE key = ?", (key,)
128
+ ).fetchone()
129
+ if row:
130
+ value_str, created_at, ttl, hits = row
131
+ if now - created_at < ttl:
132
+ # Promote TTL for hot entries
133
+ new_ttl = self._promote_ttl(hits + 1)
134
+ conn.execute(
135
+ "UPDATE cache SET hits = hits + 1, last_hit = ?, ttl = ? WHERE key = ?",
136
+ (now, new_ttl, key),
137
+ )
138
+ conn.execute(
139
+ "UPDATE stats SET total_gets = total_gets + 1, total_hits = total_hits + 1"
140
+ )
141
+ conn.commit()
142
+ val = json.loads(value_str)
143
+ self._lru_set(key, created_at, created_at + new_ttl, val)
144
+ return val
145
+ # Expired
146
+ conn.execute("DELETE FROM cache WHERE key = ?", (key,))
147
+ conn.execute("UPDATE stats SET total_misses = total_misses + 1")
148
+ conn.commit()
149
+ except Exception:
150
+ pass
151
+
152
+ try:
153
+ with sqlite3.connect(self._db_path) as conn:
154
+ conn.execute("UPDATE stats SET total_gets = total_gets + 1, total_misses = total_misses + 1")
155
+ conn.commit()
156
+ except Exception:
157
+ pass
158
+ return None
159
+
160
+ def set(self, key: str, value: Any, ttl: int | None = None) -> None:
161
+ now = time.time()
162
+ effective_ttl = ttl or self._default_ttl
163
+ expiry = now + effective_ttl
164
+
165
+ # L1
166
+ self._lru_set(key, now, expiry, value)
167
+
168
+ # L2
169
+ try:
170
+ with sqlite3.connect(self._db_path) as conn:
171
+ conn.execute(
172
+ "INSERT OR REPLACE INTO cache (key, value, created_at, ttl) VALUES (?, ?, ?, ?)",
173
+ (key, json.dumps(value, default=str), now, effective_ttl),
174
+ )
175
+ conn.execute("UPDATE stats SET total_sets = total_sets + 1")
176
+ conn.commit()
177
+ except Exception:
178
+ pass
179
+
180
+ def record_savings(self, tokens_saved: int, cost_usd: float) -> None:
181
+ """Track cumulative savings across restarts."""
182
+ try:
183
+ with sqlite3.connect(self._db_path) as conn:
184
+ conn.execute(
185
+ "UPDATE stats SET tokens_saved = tokens_saved + ?, cost_saved_usd = cost_saved_usd + ?",
186
+ (tokens_saved, cost_usd),
187
+ )
188
+ conn.commit()
189
+ except Exception:
190
+ pass
191
+
192
+ # ------------------------------------------------------------------
193
+ # Stats
194
+ # ------------------------------------------------------------------
195
+
196
+ def stats(self) -> dict[str, Any]:
197
+ try:
198
+ with sqlite3.connect(self._db_path) as conn:
199
+ total = conn.execute("SELECT COUNT(*) FROM cache").fetchone()[0]
200
+ hot = conn.execute("SELECT COUNT(*) FROM cache WHERE hits >= 5").fetchone()[0]
201
+ st = conn.execute(
202
+ "SELECT total_hits, total_misses, total_sets, tokens_saved, cost_saved_usd FROM stats WHERE id=1"
203
+ ).fetchone()
204
+ hits, misses, sets, tok_saved, cost_saved = st or (0, 0, 0, 0, 0.0)
205
+ size_bytes = self._db_path.stat().st_size if self._db_path.exists() else 0
206
+ total_lookups = (hits or 0) + (misses or 0)
207
+ return {
208
+ "total_entries": total,
209
+ "hot_entries": hot, # ≥5 hits each
210
+ "memory_entries": len(self._memory),
211
+ "total_hits": hits or 0,
212
+ "total_misses": misses or 0,
213
+ "hit_rate": round((hits or 0) / total_lookups, 4) if total_lookups else 0.0,
214
+ "tokens_saved": tok_saved or 0,
215
+ "cost_saved_usd": round(cost_saved or 0.0, 4),
216
+ "db_size_mb": round(size_bytes / 1024 / 1024, 2),
217
+ }
218
+ except Exception:
219
+ return {}
220
+
221
+ def clear(self) -> None:
222
+ self._memory.clear()
223
+ self._access_order.clear()
224
+ try:
225
+ with sqlite3.connect(self._db_path) as conn:
226
+ conn.execute("DELETE FROM cache")
227
+ conn.execute("UPDATE stats SET total_hits=0, total_misses=0, total_sets=0")
228
+ conn.commit()
229
+ except Exception:
230
+ pass
231
+
232
+ # ------------------------------------------------------------------
233
+ # LRU helpers
234
+ # ------------------------------------------------------------------
235
+
236
+ def _lru_touch(self, key: str) -> None:
237
+ try:
238
+ self._access_order.remove(key)
239
+ except ValueError:
240
+ pass
241
+ self._access_order.append(key)
242
+
243
+ def _lru_set(self, key: str, ts: float, expiry: float, value: Any) -> None:
244
+ if key in self._memory:
245
+ self._lru_touch(key)
246
+ else:
247
+ if len(self._memory) >= self._max_memory:
248
+ # Evict least-recently-used
249
+ evict = self._access_order.pop(0)
250
+ self._memory.pop(evict, None)
251
+ self._access_order.append(key)
252
+ self._memory[key] = (ts, expiry, value)
253
+
254
+ @staticmethod
255
+ def _promote_ttl(hits: int) -> float:
256
+ if hits >= 5:
257
+ return XitheadCache.HOT_TTL
258
+ if hits >= 2:
259
+ return XitheadCache.WARM_TTL
260
+ return XitheadCache.DEFAULT_TTL