Commit graph

2630 commits

Author SHA1 Message Date
chopratejas
c33f74d39d Bump to 0.5.18
- Fix image token counting (base64 no longer counted as text tokens)
- Disable CacheAligner (was inflating tokens and breaking prefix caching)
- Fix WebSocket SSL on Windows (native websockets SSL handling)
- Add transforms_summary to API responses
- Extract ProxyConfig to proxy/models.py (server.py refactor step 1)
2026-04-02 18:00:49 -07:00
chopratejas
f40dc2fbf9 Fix image token estimate: use 1600 tokens (pixel-based), not 85
Anthropic's image token formula: (width * height) / 750, capped at
~1600 tokens after auto-resize to 1568px max edge. The previous 85
was too low and the even bigger problem was that type="image"
(Anthropic format) fell through to json.dumps counting base64 as
text tokens (1MB = ~330K fake tokens).

Source: https://platform.claude.com/docs/en/build-with-claude/vision
2026-04-02 13:19:52 -07:00
chopratejas
df664d5ad9 Fix image token counting: Anthropic images counted as text tokens
Anthropic images use type="image" (not "image_url" like OpenAI).
The tokenizer's _count_content_parts fell through to the else branch
which json.dumps'd the entire base64 string and counted it as text
tokens. A 1MB image = ~330K fake "tokens", inflating savings by 10x.

Now handles type="image" with the same fixed 85-token cost as
type="image_url". This fixes the bogus 1.8B/513M token savings
reported by Windows instances using image-heavy Claude Code sessions.
2026-04-02 13:14:25 -07:00
pratikbin
944b35d78b feat: Dockerfile & ci 2026-04-03 01:02:26 +05:30
Garm
d88c6e205f Apply Ruff formatting to rebased stack 2026-04-02 13:40:57 +02:00
Garm
07fcfd493c Polish rebased display-session stack 2026-04-02 13:27:07 +02:00
Garm
02b74607e4 Format display-session tracker changes 2026-04-02 13:10:05 +02:00
Garm
460247c93f Add canonical display-session metrics for downstream dashboards 2026-04-02 13:10:05 +02:00
Garm
ec6753205a feat: add spend and input-token rollups to stats-history 2026-04-02 11:36:41 +02:00
chopratejas
0c49f6e68c Fix WebSocket SSL on Windows: use native websockets SSL handling
Manual ssl.create_default_context() + certifi doesn't load the Windows
system certificate store, causing HTTP 500 on wss:// connections to
OpenAI. Using ssl=True lets the websockets library handle SSL natively
with proper cross-platform cert store loading.
2026-04-01 15:59:49 -07:00
chopratejas
07d278a756 Disable CacheAligner: it inflates tokens and breaks prefix caching
CacheAligner was hardcoded to enabled=True in the pipeline despite
the config default being False. It extracts dynamic content from the
system prompt middle and reinserts at the end, which:
1. CHANGES the prefix bytes → provider cache miss (loses 90% discount)
2. ADDS ~341 tokens of formatting overhead per request
3. Net effect: more expensive, worse caching

Now uses the config default (enabled=False). The CacheAligner still
exists for users who explicitly opt in, but the proxy no longer
forces it on.
2026-04-01 15:57:33 -07:00
chopratejas
dc1f3ff821 Fix Codex WS proxy (Issue #86), LiteLLM metrics, and backend forwarding
- Fix WS /v1/responses: forward Sec-WebSocket-Protocol (subprotocol) to
  upstream instead of stripping it — root cause of Codex HTTP 500 errors
- Fix WS relay: handle binary messages properly instead of crashing on
  .decode(), add debug logging instead of silent except:pass
- Add Authorization header fallback from OPENAI_API_KEY env var for WS
- Extract response body from websockets InvalidStatus for error debugging
- Fix streaming /v1/responses: pass optimized_tokens (not original_tokens
  twice) so compression savings appear in streaming metrics
- Fix hardcoded provider="bedrock" in 4 metrics/log locations — now uses
  self.anthropic_backend.name so LiteLLM backends report correctly
- Forward --backend, --anyllm-provider, --region flags from wrap commands
  (codex, aider) to the proxy subprocess via _start_proxy()
- Forward API key from request headers to LiteLLM acompletion() calls
- Forward region to Vertex AI (vertex_location) not just Bedrock
- Redesign proxy startup banner: show routing table instead of misleading
  "Backend: Anthropic" label

Closes #86

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 20:06:09 -07:00
chopratejas
a4ddaa32ce Refactor: extract data models to headroom/proxy/models.py (Step 1/9)
Move ProxyConfig, RequestLog, CacheEntry, RateLimitState to
headroom/proxy/models.py. Re-exported from server.py for backward
compatibility — all existing imports continue to work.

server.py: 8835 → 8643 lines (-192)
models.py: 199 lines (new)

Part of the server.py split effort to improve maintainability.
2026-03-31 16:32:37 -07:00
chopratejas
e2d1baf250 Add transforms_summary to TransformResult and /v1/compress response
transforms_summary is a counted dict (e.g. {"router:tool_result:text": 4})
alongside the raw transforms_applied list. Cleaner display for users
without losing the raw data for debugging.
2026-03-31 14:40:28 -07:00
Tejas Chopra
32f97f2ff1
Merge pull request #81 from dorphalsig/main
feat: Add Anthropic url overrides via env vars and cli flags
2026-03-31 12:12:26 -07:00
chopratejas
e963c90b2c Fix multi-worker beacon spam, cost dashboard, upsert telemetry; bump 0.5.17
- Fix beacon spam: file lock ensures only one beacon per proxy regardless
  of worker count. Workers > 1 caused N beacons firing N rows per cycle.
- Beacon upsert: on_conflict=session_id prevents duplicate rows.
- Beacon stop() guard: skip final report if uptime < 2 minutes.
- Fix dashboard cost: savings_usd now uses model list price (monotonic),
  not moving average. Separate breakdown for compression/cache/rtk.

Fixes #83
2026-03-31 09:41:38 -07:00
chopratejas
d7ab580aa3 Fix dashboard cost savings: use model list price, not moving average
savings_usd is now tokens_saved * model list input price (monotonic,
transparent). Removed non-monotonic moving-average repricing and
confusing cost_without_headroom counterfactual.

Dashboard hero shows "Compression Savings" with clear subtitle.
Savings Breakdown section shows compression, cache, and RTK separately
with distinct colors and no scope mixing.

All beacon/telemetry fields preserved. RTK token counts still reported.

Fixes #83
2026-03-31 08:50:25 -07:00
Tejas Chopra
a99d4b9926
Merge pull request #61 from gglucass/feat/persist-savings-history
feat: persist proxy savings history
2026-03-31 08:34:59 -07:00
chopratejas
a7cc0a32f9 Fix WebSocket proxy: forward all headers + required OpenAI-Beta header
OpenAI's WebSocket Responses API requires the header
'OpenAI-Beta: responses-api=v1'. Without it, the server returns HTTP 500
on the WebSocket upgrade. Also forward all client headers (not just auth)
to upstream, skipping only hop-by-hop headers.

Tested with real OpenAI API: basic text, large tool output compression,
all working through WebSocket proxy.

Fixes #82, updates #79
2026-03-31 08:27:07 -07:00
dorphalsig
b38ef584b8 Add Anthropic url overrides via ANTHROPIC_TARGET_URL env var and --anthropic-api-url flag 2026-03-31 11:44:01 +02:00
Garm
0451585b36 Update dashboard.html 2026-03-31 11:05:05 +02:00
Garm
dd509585b0 Merge remote-tracking branch 'origin/main' into feat/persist-savings-history 2026-03-31 10:57:36 +02:00
Garm
3892774b53 style: format savings history tests 2026-03-31 10:39:08 +02:00
Garm
bb5b60f6e6 Update test_proxy_savings_history.py 2026-03-31 10:32:05 +02:00
Garm
9a695249af feat: add historical rollups and exports 2026-03-31 10:25:45 +02:00
chopratejas
e5d56aff3d Bump to 0.5.16: WebSocket proxy, Kompress ONNX, Discord link update
- WebSocket proxy for /v1/responses (Codex gpt-5.4+ support)
- Kompress ONNX INT8 text compression (no torch needed, ~100MB vs 1.5GB)
- Updated Discord invite link
- Tool_result ordering fix for Bedrock
2026-03-30 22:53:07 -07:00
chopratejas
2d97d8e900 Kompress ONNX INT8: text compression without torch dependency
KompressCompressor now tries ONNX Runtime first (156MB INT8 model),
falls back to PyTorch only if ONNX unavailable. No torch needed for
text compression — just onnxruntime (~50MB) + transformers (tokenizer).

Changes:
- Add onnxruntime + transformers to [proxy] extra in pyproject.toml
- Add _OnnxModel wrapper with get_scores/get_keep_mask interface
- _load_kompress() tries ONNX first, falls back to PyTorch
- is_kompress_available() returns True if EITHER backend available
- compress() handles both numpy (ONNX) and tensor (PyTorch) outputs

Dependency impact:
  Before: pip install headroom-ai[proxy] → no text compression
  After:  pip install headroom-ai[proxy] → Kompress ONNX INT8 (156MB)
  [ml] extra still available for full PyTorch (600MB, GPU support)
2026-03-30 22:50:13 -07:00
chopratejas
acf532bc55 Update Discord invite link across all docs 2026-03-30 22:04:05 -07:00
chopratejas
7c23a1a93a Add WebSocket proxy for /v1/responses (Codex gpt-5.4+)
Codex v0.117.0+ with newer models uses WebSocket instead of HTTP POST
for the Responses API. Added @app.websocket("/v1/responses") handler that:
- Accepts ws:// connections and forwards to wss://api.openai.com
- Compresses input on first message using existing pipeline
- Relays all response events bidirectionally
- Handles SSL (certifi), graceful disconnect, missing websockets lib

Tested with real OpenAI API: basic text, large tool outputs (200 records),
parallel function calls, instructions preservation.

Addresses #79
2026-03-30 22:00:12 -07:00
chopratejas
859a1a49ef Remove dedupe_telemetry.py from repo, add scripts/ to .gitignore
Private scripts with credentials should not be tracked in git.
2026-03-30 20:21:37 -07:00
chopratejas
606819cb94 Bump to 0.5.15: Responses API compression, tool_result ordering fix 2026-03-30 20:14:14 -07:00
chopratejas
0007a9f0f4 Add compression for OpenAI Responses API (/v1/responses)
The /v1/responses handler was passing through without compression,
meaning Codex CLI users got zero savings. Now converts Responses API
items (function_call, function_call_output, reasoning, message) to
Chat Completions format, runs the existing pipeline, and converts back.

- New: headroom/proxy/responses_converter.py — pure conversion functions
- 21 unit tests + 3 integration tests (tested with real OpenAI API)
- Preserves reasoning items, images, unknown types verbatim
- Skips compression when previous_response_id is set
- 27% compression on real Codex-pattern payloads (500 records → 14K tokens saved)

Closes #73
2026-03-30 20:13:00 -07:00
chopratejas
7d25c87286 Fix tool_result ordering: no stray user message between tool_calls and tool
Bedrock requires role=tool messages immediately after assistant tool_calls.
The previous fix inserted a user text message in between when the message
contained both text and tool_result blocks, breaking the pairing.

Drop text alongside tool_result (Claude Code never sends it in practice).
Added ordering regression tests for the Bedrock constraint.
2026-03-30 16:25:22 -07:00
chopratejas
6212644587 Fix zstd streaming decompression, add zstandard dep; bump to 0.5.14
Use stream_reader instead of decompress() for zstd request bodies.
Codex uses streaming zstd (no content size in frame header), which
causes decompress() to fail. stream_reader handles both cases.

Also added zstandard to the [proxy] optional dependencies.
2026-03-30 16:01:51 -07:00
chopratejas
8228f0edfb Fix streaming tool calls, compressed request bodies, beacon field names; bump to 0.5.13
- Fix LiteLLM stream_message: emit tool_use blocks from delta.tool_calls,
  set stop_reason from finish_reason (fixes silent MCP tool call failures)
- Fix _convert_messages_for_litellm: convert Anthropic tool_result/tool_use
  to OpenAI role=tool/tool_calls format (fixes 500 on tool round-trips)
- Fix proxy request body parsing: decompress zstd/gzip/deflate/brotli
  Content-Encoding before JSON decode (fixes Codex UnicodeDecodeError crash)
- Fix telemetry beacon field names to match /stats endpoint
  (tokens.total_before_compression, not tokens.original)
- Add dedupe_telemetry.py script for hourly Supabase row deduplication
2026-03-30 14:48:48 -07:00
chopratejas
c6d6c6a96c Switch telemetry to proxy_telemetry_v2 table, bump to 0.5.12
Telemetry beacon now writes to proxy_telemetry_v2 which has all 25
columns matching the beacon payload. Added SQL schema for the new table.
2026-03-30 10:55:45 -07:00
chopratejas
e25eb0b88a Bump to 0.5.11: fix cost calculation, Windows path decoding, unused imports 2026-03-30 09:45:35 -07:00
chopratejas
1c6ac5d3d9 Fix cost calculation: don't mix cache savings into Headroom savings
- savings_usd now = compression_savings + cli_filtering only (not cache)
- cost_with_headroom comes directly from CostTracker (not derived)
- Cache savings reported separately as provider-native feature
- Fixes: negative cost_with (-$150), inflated savings (413%), impossible savings_pct

Before: savings = compression ($11.58) + cache ($187.25) = $198.83 (wrong)
After:  savings = compression ($11.58) only. Cache reported separately.
2026-03-30 09:43:58 -07:00
chopratejas
de862d9830 fix: remove unused imports in test_langgraph.py (ruff F401) 2026-03-30 09:27:49 -07:00
chopratejas
c7731b1d21 Fix Windows drive letter path decoding in headroom learn (fixes #69)
_decode_project_path now detects single-letter first component as a
Windows drive letter: -C-MQ2-macros → C:\MQ2\macros instead of
/C/MQ2/macros (which becomes \\C\MQ2\macros on Windows).

- Add Windows drive detection before Unix path attempts
- Fix fallback path construction for Windows patterns
- Add Linux /home/ support in greedy decoder
- Add 2 tests for Windows drive letter patterns
2026-03-30 09:08:21 -07:00
gglucass
165d3ca726
Merge branch 'main' into feat/persist-savings-history 2026-03-30 12:04:39 +02:00
Garm
3b887ff79a test: cover savings tracker branches 2026-03-30 12:02:03 +02:00
Garm
66a7b6e97e Update savings_tracker.py 2026-03-30 11:57:26 +02:00
Garm
3652bcb182 fix: restore anthropic compression when hooks enabled 2026-03-30 11:25:15 +02:00
Garm
32ae62532f refactor: remove dashboard storage path label 2026-03-30 10:56:28 +02:00
Garm
06c1b32cdf refactor: move dashboard mode switch to header 2026-03-30 10:54:46 +02:00
Tejas Chopra
42ea085751
Merge pull request #60 from gglucass/feat/add-headroom-sdk
feat: allow overriding proxy telemetry sdk via HEADROOM_SDK
2026-03-29 18:21:13 -07:00
Tejas Chopra
4ae9bd3c1b
Merge pull request #68 from KunalLohtia/feat/langgraph-compress-tool-messages
feat: LangGraph compress_tool_messages node for ToolMessage compression
2026-03-29 18:20:08 -07:00
kunallohtia
9590dbc214 feat: add LangGraph compress_tool_messages node for ToolMessage compression 2026-03-29 17:11:15 -07:00
chopratejas
95f9e7a3e0 docs: add OpenClaw plugin to README, integration guide, index, and TS SDK docs 2026-03-29 00:02:05 -07:00