smartcontext-proxy 0.1.0 → 0.2.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.
- package/PLAN-v2.md +390 -0
- package/dist/src/context/ab-test.d.ts +32 -0
- package/dist/src/context/ab-test.js +133 -0
- package/dist/src/index.js +99 -78
- package/dist/src/proxy/classifier.d.ts +14 -0
- package/dist/src/proxy/classifier.js +63 -0
- package/dist/src/proxy/connect-proxy.d.ts +34 -0
- package/dist/src/proxy/connect-proxy.js +167 -0
- package/dist/src/proxy/tls-interceptor.d.ts +23 -0
- package/dist/src/proxy/tls-interceptor.js +211 -0
- package/dist/src/proxy/tunnel.d.ts +7 -0
- package/dist/src/proxy/tunnel.js +33 -0
- package/dist/src/system/installer.d.ts +25 -0
- package/dist/src/system/installer.js +180 -0
- package/dist/src/system/linux.d.ts +11 -0
- package/dist/src/system/linux.js +60 -0
- package/dist/src/system/macos.d.ts +24 -0
- package/dist/src/system/macos.js +98 -0
- package/dist/src/system/watchdog.d.ts +7 -0
- package/dist/src/system/watchdog.js +115 -0
- package/dist/src/test/connect-proxy.test.d.ts +1 -0
- package/dist/src/test/connect-proxy.test.js +147 -0
- package/dist/src/tls/ca-manager.d.ts +9 -0
- package/dist/src/tls/ca-manager.js +117 -0
- package/dist/src/tls/trust-store.d.ts +11 -0
- package/dist/src/tls/trust-store.js +121 -0
- package/dist/src/tray/bridge.d.ts +8 -0
- package/dist/src/tray/bridge.js +66 -0
- package/dist/src/ui/ws-feed.d.ts +8 -0
- package/dist/src/ui/ws-feed.js +30 -0
- package/native/macos/SmartContextTray/Package.swift +13 -0
- package/native/macos/SmartContextTray/Sources/main.swift +206 -0
- package/package.json +6 -2
- package/src/context/ab-test.ts +172 -0
- package/src/index.ts +104 -74
- package/src/proxy/classifier.ts +71 -0
- package/src/proxy/connect-proxy.ts +187 -0
- package/src/proxy/tls-interceptor.ts +261 -0
- package/src/proxy/tunnel.ts +32 -0
- package/src/system/installer.ts +148 -0
- package/src/system/linux.ts +57 -0
- package/src/system/macos.ts +89 -0
- package/src/system/watchdog.ts +76 -0
- package/src/test/connect-proxy.test.ts +170 -0
- package/src/tls/ca-manager.ts +140 -0
- package/src/tls/trust-store.ts +123 -0
- package/src/tray/bridge.ts +61 -0
- package/src/ui/ws-feed.ts +32 -0
package/PLAN-v2.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# SmartContext Proxy v2.0 — Transparent Network Interceptor
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
One-command install transparent proxy that automatically intercepts all LLM API traffic from any app on the system, optimizes context windows, and provides real-time control via system tray + web dashboard.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Any App (CC, OC, curl, Python, browser...)
|
|
11
|
+
│
|
|
12
|
+
│ HTTPS → api.anthropic.com:443
|
|
13
|
+
│
|
|
14
|
+
▼
|
|
15
|
+
[macOS System Proxy / pf / Linux iptables]
|
|
16
|
+
│
|
|
17
|
+
▼
|
|
18
|
+
┌──────────────────────────────────────┐
|
|
19
|
+
│ SmartContext Proxy (:4800) │
|
|
20
|
+
│ │
|
|
21
|
+
│ ┌─ CONNECT Handler ──────────────┐ │
|
|
22
|
+
│ │ Incoming HTTPS CONNECT │ │
|
|
23
|
+
│ │ ↓ │ │
|
|
24
|
+
│ │ Is hostname an LLM provider? │ │
|
|
25
|
+
│ │ ├─ NO → TCP tunnel (zero cost)│ │
|
|
26
|
+
│ │ └─ YES → TLS intercept ──┐ │ │
|
|
27
|
+
│ └────────────────────────────┘ │ │
|
|
28
|
+
│ │ │
|
|
29
|
+
│ ┌─ LLM Interceptor ──────────┐ │ │
|
|
30
|
+
│ │ TLS terminate (local CA) │ │ │
|
|
31
|
+
│ │ Parse request │ │ │
|
|
32
|
+
│ │ Optimize context │ │ │
|
|
33
|
+
│ │ Forward to real provider │ │ │
|
|
34
|
+
│ │ Stream response back │ │ │
|
|
35
|
+
│ │ Index exchange (async) │ │ │
|
|
36
|
+
│ └─────────────────────────────┘ │ │
|
|
37
|
+
│ │
|
|
38
|
+
│ ┌─ Dashboard (:4800/) ───────────┐ │
|
|
39
|
+
│ │ Real-time stats, live feed │ │
|
|
40
|
+
│ │ Savings, controls, settings │ │
|
|
41
|
+
│ └────────────────────────────────┘ │
|
|
42
|
+
│ │
|
|
43
|
+
│ ┌─ System Tray ──────────────────┐ │
|
|
44
|
+
│ │ Status icon (green/yellow/red)│ │
|
|
45
|
+
│ │ Click → open dashboard │ │
|
|
46
|
+
│ │ Quick: pause/resume/quit │ │
|
|
47
|
+
│ │ Tooltip: "142 req, $63 saved" │ │
|
|
48
|
+
│ └────────────────────────────────┘ │
|
|
49
|
+
└──────────────────────────────────────┘
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
Real LLM Provider (api.anthropic.com, api.openai.com, ...)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Implementation Phases
|
|
56
|
+
|
|
57
|
+
### Phase 1: HTTPS CONNECT Proxy + Selective MITM
|
|
58
|
+
|
|
59
|
+
**Goal**: Proxy intercepts HTTPS traffic, passes through non-LLM, MITM's LLM endpoints.
|
|
60
|
+
|
|
61
|
+
#### 1.1 Local CA Certificate Manager
|
|
62
|
+
- Generate RSA 2048 root CA cert + key on first run
|
|
63
|
+
- Store in `~/.smartcontext/ca/` (root-ca.crt, root-ca.key)
|
|
64
|
+
- Generate per-hostname certs on-the-fly (cached in `~/.smartcontext/ca/hosts/`)
|
|
65
|
+
- Uses `node-forge` for cert generation (pure JS, no native deps)
|
|
66
|
+
- **Files**: `src/tls/ca-manager.ts`
|
|
67
|
+
|
|
68
|
+
#### 1.2 CA Trust Store Installation
|
|
69
|
+
- macOS: `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain`
|
|
70
|
+
- Linux: copy to `/usr/local/share/ca-certificates/`, run `update-ca-certificates`
|
|
71
|
+
- Node.js: set `NODE_EXTRA_CA_CERTS` env var
|
|
72
|
+
- Uninstall: reverse all of the above
|
|
73
|
+
- **Files**: `src/tls/trust-store.ts`
|
|
74
|
+
|
|
75
|
+
#### 1.3 CONNECT Proxy Server
|
|
76
|
+
- HTTP proxy server handling CONNECT method
|
|
77
|
+
- For non-LLM hosts: blind TCP tunnel (zero overhead, no TLS termination)
|
|
78
|
+
- For LLM hosts: TLS intercept with generated cert
|
|
79
|
+
- Known LLM hosts list: `api.anthropic.com`, `api.openai.com`, `generativelanguage.googleapis.com`, `openrouter.ai`, `localhost:11434` (Ollama)
|
|
80
|
+
- Configurable host patterns for custom providers
|
|
81
|
+
- **Files**: `src/proxy/connect-proxy.ts`, `src/proxy/tunnel.ts`
|
|
82
|
+
|
|
83
|
+
#### 1.4 TLS Interceptor
|
|
84
|
+
- Terminate incoming TLS with per-host cert (signed by local CA)
|
|
85
|
+
- Parse HTTP request inside tunnel
|
|
86
|
+
- Apply context optimization
|
|
87
|
+
- Establish new TLS connection to real provider
|
|
88
|
+
- Stream response back through tunnel
|
|
89
|
+
- **Files**: `src/proxy/tls-interceptor.ts`
|
|
90
|
+
|
|
91
|
+
#### 1.5 Traffic Classifier
|
|
92
|
+
- Hostname → provider mapping (exact match + patterns)
|
|
93
|
+
- Path-based detection (`/v1/messages`, `/v1/chat/completions`, `/api/chat`)
|
|
94
|
+
- Configurable allowlist/blocklist
|
|
95
|
+
- **Files**: `src/proxy/classifier.ts`
|
|
96
|
+
|
|
97
|
+
#### Phase 1 Tests
|
|
98
|
+
- [ ] Non-LLM HTTPS traffic passes through untouched
|
|
99
|
+
- [ ] LLM traffic is intercepted and optimized
|
|
100
|
+
- [ ] Cert generated for api.anthropic.com on first request
|
|
101
|
+
- [ ] Streaming works through MITM tunnel
|
|
102
|
+
- [ ] Performance: <2ms overhead for non-LLM tunnel
|
|
103
|
+
- [ ] Ollama local traffic intercepted (HTTP, no TLS needed)
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### Phase 2: System Integration (Auto-Configuration)
|
|
108
|
+
|
|
109
|
+
**Goal**: One command configures the OS to route traffic through SmartContext.
|
|
110
|
+
|
|
111
|
+
#### 2.1 macOS System Proxy Configuration
|
|
112
|
+
- `networksetup -setsecurewebproxy <interface> 127.0.0.1 4800`
|
|
113
|
+
- `networksetup -setwebproxy <interface> 127.0.0.1 4800`
|
|
114
|
+
- Auto-detect active network interface (Wi-Fi, Ethernet)
|
|
115
|
+
- PAC file for selective proxying (only LLM hosts through proxy)
|
|
116
|
+
- Bypass list for local traffic (localhost except Ollama port)
|
|
117
|
+
- **Files**: `src/system/macos.ts`
|
|
118
|
+
|
|
119
|
+
#### 2.2 Linux System Proxy Configuration
|
|
120
|
+
- Set `http_proxy`/`https_proxy` env vars system-wide
|
|
121
|
+
- GNOME: `gsettings set org.gnome.system.proxy`
|
|
122
|
+
- Alternatively: iptables REDIRECT rules for specific IPs
|
|
123
|
+
- **Files**: `src/system/linux.ts`
|
|
124
|
+
|
|
125
|
+
#### 2.3 Install / Uninstall Commands
|
|
126
|
+
- `smartcontext-proxy install`:
|
|
127
|
+
1. Generate CA cert
|
|
128
|
+
2. Install CA in trust store (requires sudo)
|
|
129
|
+
3. Configure system proxy
|
|
130
|
+
4. Start daemon
|
|
131
|
+
5. Install system service (auto-start on boot)
|
|
132
|
+
- `smartcontext-proxy uninstall`:
|
|
133
|
+
1. Stop daemon
|
|
134
|
+
2. Remove system proxy config
|
|
135
|
+
3. Remove CA from trust store
|
|
136
|
+
4. Remove system service
|
|
137
|
+
5. Optionally delete data (`--purge`)
|
|
138
|
+
- Atomic: if any step fails, rollback all previous
|
|
139
|
+
- **Files**: `src/system/installer.ts`
|
|
140
|
+
|
|
141
|
+
#### 2.4 PAC File Server
|
|
142
|
+
- Serve proxy auto-config at `http://localhost:4800/proxy.pac`
|
|
143
|
+
- Routes only LLM hostnames through proxy
|
|
144
|
+
- Everything else DIRECT
|
|
145
|
+
- Auto-update when provider list changes
|
|
146
|
+
- **Files**: `src/system/pac.ts`
|
|
147
|
+
|
|
148
|
+
#### Phase 2 Tests
|
|
149
|
+
- [ ] `install` configures system proxy on macOS
|
|
150
|
+
- [ ] `uninstall` cleanly removes everything
|
|
151
|
+
- [ ] PAC file correctly routes LLM traffic only
|
|
152
|
+
- [ ] Non-LLM browsing unaffected after install
|
|
153
|
+
- [ ] Works on both Wi-Fi and Ethernet
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Phase 3: System Tray Application
|
|
158
|
+
|
|
159
|
+
**Goal**: Native system tray icon with real-time status and quick controls.
|
|
160
|
+
|
|
161
|
+
#### 3.1 Tray Technology
|
|
162
|
+
- Use `@nicolo-ribaudo/trayicon` or custom native bridge
|
|
163
|
+
- macOS: NSStatusItem via Objective-C bridge (or Swift)
|
|
164
|
+
- Fallback: Electron-free `menubar`-style with native popup
|
|
165
|
+
- Alternative: spawn a tiny Swift binary for tray (compiled at install time)
|
|
166
|
+
- **Decision needed**: pure Node.js tray lib vs native Swift helper
|
|
167
|
+
|
|
168
|
+
#### 3.2 Tray Icon States
|
|
169
|
+
- 🟢 Green: running, optimizing
|
|
170
|
+
- 🟡 Yellow: paused (pass-through mode)
|
|
171
|
+
- 🔴 Red: error / proxy down
|
|
172
|
+
- Icon shows miniature savings counter
|
|
173
|
+
|
|
174
|
+
#### 3.3 Tray Menu
|
|
175
|
+
```
|
|
176
|
+
SmartContext Proxy ● Running
|
|
177
|
+
─────────────────────────────
|
|
178
|
+
Today: 142 requests, $63 saved
|
|
179
|
+
Savings: 68% avg
|
|
180
|
+
─────────────────────────────
|
|
181
|
+
Open Dashboard ⌘D
|
|
182
|
+
─────────────────────────────
|
|
183
|
+
☑ Optimize Context
|
|
184
|
+
☐ A/B Test Mode
|
|
185
|
+
☐ Debug Headers
|
|
186
|
+
─────────────────────────────
|
|
187
|
+
Pause Optimization
|
|
188
|
+
─────────────────────────────
|
|
189
|
+
Settings...
|
|
190
|
+
Logs...
|
|
191
|
+
─────────────────────────────
|
|
192
|
+
Quit SmartContext ⌘Q
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### 3.4 Tray Click → Dashboard
|
|
196
|
+
- Left click: open `http://localhost:4800` in default browser
|
|
197
|
+
- Right click: show menu above
|
|
198
|
+
- Tooltip on hover: "SmartContext: 142 req, $63 saved, 68% avg"
|
|
199
|
+
|
|
200
|
+
#### 3.5 Notifications
|
|
201
|
+
- macOS native notifications for:
|
|
202
|
+
- "SmartContext installed successfully"
|
|
203
|
+
- "Optimization paused"
|
|
204
|
+
- "⚠️ Quality drop detected (A/B test)"
|
|
205
|
+
- Daily summary: "Saved $X today (Y requests)"
|
|
206
|
+
|
|
207
|
+
#### 3.6 Native macOS Tray (Swift helper)
|
|
208
|
+
- Tiny Swift binary (~50KB) for NSStatusItem
|
|
209
|
+
- Communicates with Node.js proxy via localhost HTTP API
|
|
210
|
+
- Polls `/_sc/status` every 2s for icon updates
|
|
211
|
+
- Compiled at `npm install` time via `swift build`
|
|
212
|
+
- **Files**: `native/macos/SmartContextTray/`, `src/tray/bridge.ts`
|
|
213
|
+
|
|
214
|
+
#### Phase 3 Tests
|
|
215
|
+
- [ ] Tray icon appears on macOS
|
|
216
|
+
- [ ] Icon color changes with state
|
|
217
|
+
- [ ] Menu items trigger correct API calls
|
|
218
|
+
- [ ] Click opens dashboard
|
|
219
|
+
- [ ] Notifications fire on key events
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### Phase 4: Full Dashboard Upgrade
|
|
224
|
+
|
|
225
|
+
**Goal**: Production-grade dashboard matching SPEC.md screens.
|
|
226
|
+
|
|
227
|
+
#### 4.1 Dashboard Screens (from SPEC)
|
|
228
|
+
- **Home/Status**: savings cards, 7-day chart, provider status, performance
|
|
229
|
+
- **Live Feed**: WebSocket real-time stream, expandable rows with retrieval details
|
|
230
|
+
- **Sessions**: per-session breakdown, exchange viewer, export JSON
|
|
231
|
+
- **Savings Report**: monthly breakdown, by-provider, by-model, projections
|
|
232
|
+
- **Settings**: context tuning, provider management, logging, toggles
|
|
233
|
+
- **A/B Test Results**: comparison table, quality match rates, diff inspector
|
|
234
|
+
|
|
235
|
+
#### 4.2 WebSocket Live Feed
|
|
236
|
+
- `ws://localhost:4800/_sc/ws` — real-time request stream
|
|
237
|
+
- Push every request metric as it completes
|
|
238
|
+
- Dashboard auto-updates without polling
|
|
239
|
+
- **Files**: `src/ui/ws-feed.ts`
|
|
240
|
+
|
|
241
|
+
#### 4.3 Charts
|
|
242
|
+
- Lightweight `<canvas>` drawing (no chart library)
|
|
243
|
+
- 7-day savings trend (bar chart)
|
|
244
|
+
- Daily request volume (line chart)
|
|
245
|
+
- Per-provider pie chart
|
|
246
|
+
- **Files**: `src/ui/charts.ts`
|
|
247
|
+
|
|
248
|
+
#### 4.4 Settings UI
|
|
249
|
+
- Edit all context config from browser
|
|
250
|
+
- Toggle pause/resume per provider
|
|
251
|
+
- Manage LLM host patterns (add custom providers)
|
|
252
|
+
- Debug mode toggle
|
|
253
|
+
- Changes write to `~/.smartcontext/config.json`
|
|
254
|
+
|
|
255
|
+
#### Phase 4 Tests
|
|
256
|
+
- [ ] All 6 dashboard screens render
|
|
257
|
+
- [ ] WebSocket feed updates in real-time
|
|
258
|
+
- [ ] Charts display correct data
|
|
259
|
+
- [ ] Settings changes persist
|
|
260
|
+
- [ ] Dashboard loads in <200ms
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Phase 5: A/B Test Mode + LLM Diagnostics
|
|
265
|
+
|
|
266
|
+
**Goal**: Quality validation and self-tuning.
|
|
267
|
+
|
|
268
|
+
#### 5.1 A/B Test Mode
|
|
269
|
+
- Every request sent twice (optimized + original)
|
|
270
|
+
- Compare: semantic similarity, token delta, tool use match
|
|
271
|
+
- Dashboard shows quality match rates
|
|
272
|
+
- `--test-sample N%` for cost control
|
|
273
|
+
|
|
274
|
+
#### 5.2 LLM-Assisted Diagnostics
|
|
275
|
+
- On quality diff <0.85: diagnostic LLM analyzes what went wrong
|
|
276
|
+
- Auto-tune after 50+ requests
|
|
277
|
+
- `smartcontext-proxy diagnose` CLI command
|
|
278
|
+
- Dashboard "Diagnose" button per request
|
|
279
|
+
|
|
280
|
+
#### 5.3 Auto-Tuning
|
|
281
|
+
- Analyze score distributions, miss patterns, chunk sizes
|
|
282
|
+
- Propose config changes with reasoning
|
|
283
|
+
- User approves/rejects from dashboard
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Phase 6: Production Hardening
|
|
288
|
+
|
|
289
|
+
#### 6.1 Upstream Proxy Support
|
|
290
|
+
- Detect and chain through corporate proxies
|
|
291
|
+
- `HTTP_PROXY`/`HTTPS_PROXY` awareness
|
|
292
|
+
- Proxy authentication (Basic, NTLM)
|
|
293
|
+
|
|
294
|
+
#### 6.2 Cert Pinning Fallback
|
|
295
|
+
- Detect cert pinning failures (TLS handshake error)
|
|
296
|
+
- Auto-fallback to tunnel mode for pinned hosts
|
|
297
|
+
- Log which apps use pinning
|
|
298
|
+
|
|
299
|
+
#### 6.3 Performance
|
|
300
|
+
- Connection pooling to providers
|
|
301
|
+
- Cert caching (no regeneration per request)
|
|
302
|
+
- Non-LLM tunnel: zero-copy forwarding
|
|
303
|
+
- Benchmark: <5ms p95 overhead for LLM, <1ms for tunnel
|
|
304
|
+
|
|
305
|
+
#### 6.4 Security
|
|
306
|
+
- CA private key encrypted at rest (AES-256, machine-derived key)
|
|
307
|
+
- No plaintext API keys (env var references)
|
|
308
|
+
- Localhost-only binding
|
|
309
|
+
- Auto-lock if proxy crashes (remove system proxy config)
|
|
310
|
+
|
|
311
|
+
#### 6.5 Error Recovery
|
|
312
|
+
- Crash guard: if proxy dies, system proxy auto-removed (watchdog)
|
|
313
|
+
- Graceful degradation: if optimization fails, tunnel through
|
|
314
|
+
- Health monitor: auto-restart on failure
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Tech Stack
|
|
319
|
+
|
|
320
|
+
| Component | Technology |
|
|
321
|
+
|-----------|-----------|
|
|
322
|
+
| Proxy core | Node.js, raw `http`/`https`/`tls`/`net` modules |
|
|
323
|
+
| Cert generation | `node-forge` (pure JS) |
|
|
324
|
+
| Vector storage | LanceDB embedded |
|
|
325
|
+
| Embeddings | Ollama (GPU) / ONNX (CPU fallback) |
|
|
326
|
+
| Dashboard | Vanilla HTML/CSS/JS, WebSocket |
|
|
327
|
+
| System tray (macOS) | Swift native binary (~50KB) |
|
|
328
|
+
| System tray (Linux) | libappindicator / StatusNotifierItem |
|
|
329
|
+
| System config | `networksetup` (macOS), `gsettings`/env (Linux) |
|
|
330
|
+
| Token counting | `tiktoken` |
|
|
331
|
+
|
|
332
|
+
## Dependencies (new)
|
|
333
|
+
|
|
334
|
+
- `node-forge` — TLS cert generation (pure JS)
|
|
335
|
+
- `ws` — WebSocket server for live feed
|
|
336
|
+
|
|
337
|
+
## File Structure (v2)
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
smartcontext-proxy/
|
|
341
|
+
├── src/
|
|
342
|
+
│ ├── index.ts # CLI + entry point
|
|
343
|
+
│ ├── proxy/
|
|
344
|
+
│ │ ├── connect-proxy.ts # HTTP CONNECT proxy server
|
|
345
|
+
│ │ ├── tunnel.ts # Blind TCP tunnel (non-LLM)
|
|
346
|
+
│ │ ├── tls-interceptor.ts # TLS MITM for LLM traffic
|
|
347
|
+
│ │ ├── classifier.ts # LLM vs non-LLM traffic detection
|
|
348
|
+
│ │ ├── server.ts # (existing) request handling
|
|
349
|
+
│ │ ├── router.ts # (existing) provider routing
|
|
350
|
+
│ │ └── stream.ts # (existing) SSE pass-through
|
|
351
|
+
│ ├── tls/
|
|
352
|
+
│ │ ├── ca-manager.ts # Root CA generation + per-host certs
|
|
353
|
+
│ │ └── trust-store.ts # OS trust store install/uninstall
|
|
354
|
+
│ ├── system/
|
|
355
|
+
│ │ ├── installer.ts # install/uninstall orchestrator
|
|
356
|
+
│ │ ├── macos.ts # macOS proxy + keychain config
|
|
357
|
+
│ │ ├── linux.ts # Linux proxy config
|
|
358
|
+
│ │ └── pac.ts # PAC file generator/server
|
|
359
|
+
│ ├── tray/
|
|
360
|
+
│ │ └── bridge.ts # Node.js ↔ native tray communication
|
|
361
|
+
│ ├── providers/ # (existing)
|
|
362
|
+
│ ├── context/ # (existing)
|
|
363
|
+
│ ├── embedding/ # (existing)
|
|
364
|
+
│ ├── storage/ # (existing)
|
|
365
|
+
│ ├── metrics/ # (existing)
|
|
366
|
+
│ ├── ui/
|
|
367
|
+
│ │ ├── dashboard.ts # (upgrade) full dashboard
|
|
368
|
+
│ │ ├── ws-feed.ts # WebSocket live feed
|
|
369
|
+
│ │ └── charts.ts # Canvas chart rendering
|
|
370
|
+
│ ├── daemon/ # (existing)
|
|
371
|
+
│ ├── config/ # (existing)
|
|
372
|
+
│ └── test/
|
|
373
|
+
├── native/
|
|
374
|
+
│ └── macos/
|
|
375
|
+
│ └── SmartContextTray/ # Swift tray app
|
|
376
|
+
├── adapters/openclaw/ # (existing)
|
|
377
|
+
├── package.json
|
|
378
|
+
└── README.md
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Execution Order
|
|
382
|
+
|
|
383
|
+
1. **Phase 1** (HTTPS CONNECT + MITM) — foundational, everything depends on this
|
|
384
|
+
2. **Phase 2** (System integration) — makes it actually transparent
|
|
385
|
+
3. **Phase 3** (System tray) — user control from minute one
|
|
386
|
+
4. **Phase 4** (Dashboard upgrade) — full observability
|
|
387
|
+
5. **Phase 5** (A/B + diagnostics) — quality assurance
|
|
388
|
+
6. **Phase 6** (Hardening) — production readiness
|
|
389
|
+
|
|
390
|
+
Phases 3+4 can run in parallel after Phase 2.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ABComparison {
|
|
2
|
+
requestId: number;
|
|
3
|
+
timestamp: number;
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
optimizedTokens: number;
|
|
7
|
+
originalTokens: number;
|
|
8
|
+
savingsPercent: number;
|
|
9
|
+
semanticSimilarity: number;
|
|
10
|
+
qualityMatch: boolean;
|
|
11
|
+
responseA: string;
|
|
12
|
+
responseB: string;
|
|
13
|
+
latencyA: number;
|
|
14
|
+
latencyB: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function enableABTest(sampleRate?: number): void;
|
|
17
|
+
export declare function disableABTest(): void;
|
|
18
|
+
export declare function isABEnabled(): boolean;
|
|
19
|
+
export declare function shouldABTest(): boolean;
|
|
20
|
+
export declare function getABResults(limit?: number): ABComparison[];
|
|
21
|
+
export declare function getABSummary(): {
|
|
22
|
+
total: number;
|
|
23
|
+
qualityMatch: number;
|
|
24
|
+
minorDiff: number;
|
|
25
|
+
significantDiff: number;
|
|
26
|
+
avgSavings: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Send the original (unoptimized) request to the provider for comparison.
|
|
30
|
+
* This is Path B — the result is stored but NOT returned to the client.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sendOriginalForComparison(originalBody: Buffer, hostname: string, port: number, path: string, headers: Record<string, string>, requestId: number, provider: string, model: string, optimizedResponse: string, optimizedTokens: number, originalTokens: number, savingsPercent: number): Promise<void>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.enableABTest = enableABTest;
|
|
7
|
+
exports.disableABTest = disableABTest;
|
|
8
|
+
exports.isABEnabled = isABEnabled;
|
|
9
|
+
exports.shouldABTest = shouldABTest;
|
|
10
|
+
exports.getABResults = getABResults;
|
|
11
|
+
exports.getABSummary = getABSummary;
|
|
12
|
+
exports.sendOriginalForComparison = sendOriginalForComparison;
|
|
13
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
14
|
+
const node_https_1 = __importDefault(require("node:https"));
|
|
15
|
+
let abResults = [];
|
|
16
|
+
let abEnabled = false;
|
|
17
|
+
let abSampleRate = 100; // percent
|
|
18
|
+
function enableABTest(sampleRate = 100) {
|
|
19
|
+
abEnabled = true;
|
|
20
|
+
abSampleRate = Math.min(100, Math.max(1, sampleRate));
|
|
21
|
+
}
|
|
22
|
+
function disableABTest() {
|
|
23
|
+
abEnabled = false;
|
|
24
|
+
}
|
|
25
|
+
function isABEnabled() {
|
|
26
|
+
return abEnabled;
|
|
27
|
+
}
|
|
28
|
+
function shouldABTest() {
|
|
29
|
+
if (!abEnabled)
|
|
30
|
+
return false;
|
|
31
|
+
return Math.random() * 100 < abSampleRate;
|
|
32
|
+
}
|
|
33
|
+
function getABResults(limit = 50) {
|
|
34
|
+
return abResults.slice(-limit);
|
|
35
|
+
}
|
|
36
|
+
function getABSummary() {
|
|
37
|
+
const total = abResults.length;
|
|
38
|
+
if (total === 0)
|
|
39
|
+
return { total: 0, qualityMatch: 0, minorDiff: 0, significantDiff: 0, avgSavings: 0 };
|
|
40
|
+
let qualityMatch = 0;
|
|
41
|
+
let minorDiff = 0;
|
|
42
|
+
let significantDiff = 0;
|
|
43
|
+
let totalSavings = 0;
|
|
44
|
+
for (const r of abResults) {
|
|
45
|
+
if (r.semanticSimilarity >= 0.95)
|
|
46
|
+
qualityMatch++;
|
|
47
|
+
else if (r.semanticSimilarity >= 0.85)
|
|
48
|
+
minorDiff++;
|
|
49
|
+
else
|
|
50
|
+
significantDiff++;
|
|
51
|
+
totalSavings += r.savingsPercent;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
total,
|
|
55
|
+
qualityMatch,
|
|
56
|
+
minorDiff,
|
|
57
|
+
significantDiff,
|
|
58
|
+
avgSavings: Math.round(totalSavings / total),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Send the original (unoptimized) request to the provider for comparison.
|
|
63
|
+
* This is Path B — the result is stored but NOT returned to the client.
|
|
64
|
+
*/
|
|
65
|
+
async function sendOriginalForComparison(originalBody, hostname, port, path, headers, requestId, provider, model, optimizedResponse, optimizedTokens, originalTokens, savingsPercent) {
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
try {
|
|
68
|
+
const useHTTPS = port === 443 || hostname.includes('.');
|
|
69
|
+
const transport = useHTTPS ? node_https_1.default : node_http_1.default;
|
|
70
|
+
const url = `${useHTTPS ? 'https' : 'http'}://${hostname}:${port}${path}`;
|
|
71
|
+
const forwardHeaders = { ...headers };
|
|
72
|
+
forwardHeaders['content-length'] = String(originalBody.length);
|
|
73
|
+
const responseB = await new Promise((resolve, reject) => {
|
|
74
|
+
const req = transport.request(url, { method: 'POST', headers: forwardHeaders }, (res) => {
|
|
75
|
+
let data = '';
|
|
76
|
+
res.on('data', (chunk) => (data += chunk));
|
|
77
|
+
res.on('end', () => resolve(data));
|
|
78
|
+
});
|
|
79
|
+
req.on('error', reject);
|
|
80
|
+
req.write(originalBody);
|
|
81
|
+
req.end();
|
|
82
|
+
});
|
|
83
|
+
const latencyB = Date.now() - startTime;
|
|
84
|
+
// Simple text similarity (cosine of character trigrams)
|
|
85
|
+
const similarity = textSimilarity(optimizedResponse, responseB);
|
|
86
|
+
abResults.push({
|
|
87
|
+
requestId,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
provider,
|
|
90
|
+
model,
|
|
91
|
+
optimizedTokens,
|
|
92
|
+
originalTokens,
|
|
93
|
+
savingsPercent,
|
|
94
|
+
semanticSimilarity: similarity,
|
|
95
|
+
qualityMatch: similarity >= 0.95,
|
|
96
|
+
responseA: optimizedResponse.slice(0, 500),
|
|
97
|
+
responseB: responseB.slice(0, 500),
|
|
98
|
+
latencyA: 0, // filled externally
|
|
99
|
+
latencyB,
|
|
100
|
+
});
|
|
101
|
+
// Keep max 1000 results
|
|
102
|
+
if (abResults.length > 1000) {
|
|
103
|
+
abResults = abResults.slice(-500);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// A/B test failure is non-critical
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Simple text similarity using character trigrams */
|
|
111
|
+
function textSimilarity(a, b) {
|
|
112
|
+
if (a === b)
|
|
113
|
+
return 1.0;
|
|
114
|
+
if (!a || !b)
|
|
115
|
+
return 0;
|
|
116
|
+
const trigramsA = getTrigrams(a.toLowerCase());
|
|
117
|
+
const trigramsB = getTrigrams(b.toLowerCase());
|
|
118
|
+
let intersection = 0;
|
|
119
|
+
for (const t of trigramsA) {
|
|
120
|
+
if (trigramsB.has(t))
|
|
121
|
+
intersection++;
|
|
122
|
+
}
|
|
123
|
+
const union = trigramsA.size + trigramsB.size - intersection;
|
|
124
|
+
return union === 0 ? 0 : intersection / union;
|
|
125
|
+
}
|
|
126
|
+
function getTrigrams(s) {
|
|
127
|
+
const set = new Set();
|
|
128
|
+
for (let i = 0; i < s.length - 2; i++) {
|
|
129
|
+
set.add(s.substring(i, i + 3));
|
|
130
|
+
}
|
|
131
|
+
return set;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=ab-test.js.map
|