Commit graph

2630 commits

Author SHA1 Message Date
chopratejas
94a6cd8d99 Add pluggable adapter hooks for CCR, Storage, and TOIN backends
Enable SaaS packages to inject custom backends (Redis, PostgreSQL, etc.)
without forking OSS, using entry_points and ContextVars for tenant isolation.

CCR: Request-scoped ContextVar + HEADROOM_CCR_BACKEND env + entry_point loading
Storage: URL-scheme entry_point resolution for custom storage backends
TOIN: New TOINBackend protocol + FileSystemTOINBackend (extracted from toin.py)
      + HEADROOM_TOIN_BACKEND env + entry_point loading

31 tests covering protocol conformance, backend wiring, entry_point loading,
ContextVar thread isolation, and end-to-end adapter lifecycle.
2026-02-15 17:47:30 -08:00
chopratejas
636abd22f3 Add tests for streaming resilience and concurrent session safety
Tests cover model resolution caching (10 tests), streaming error
handling for httpx errors (7 tests), concurrent session safety (4 tests),
and cost tracking accuracy without cache double-counting (3 tests).
2026-02-11 11:24:12 -08:00
chopratejas
4cdaa49153 Fix streaming crashes under concurrent sessions
- Cache LiteLLM model resolution to avoid synchronous cost_per_token()
  calls blocking the async event loop on every request (3.28ms → 0.12ms)
- Add proper error handling in streaming generate() for httpx connection
  errors, timeouts, and pool exhaustion — previously crashed ASGI app
- Bump version to 0.3.3
2026-02-11 10:52:51 -08:00
angpt
4481b514e7 Update anyllm.py 2026-02-11 10:17:13 -06:00
chopratejas
ae59ec8cc0 Bump version to 0.3.2 2026-02-10 22:44:33 -08:00
chopratejas
e8911efe90 Add adaptive compression sizing and fix cost tracking bugs
- Fix LiteLLM pricing for claude-opus-4-6 by adding provider prefix
  fallback (_resolve_litellm_model) when model name is unrecognized
- Fix cost savings double-counting: remove cache_read/cache_write
  tokens from original_cost calculation at all 3 call sites, which
  was inflating reported savings ~425x
- Add information saturation engine (adaptive_sizer.py) using Kneedle
  algorithm on unique bigram coverage curves to statistically determine
  optimal compression K instead of hardcoded thresholds
- Add CompressionProfile with per-tool bias multipliers (conservative,
  moderate, aggressive) configurable via --tool-profile CLI flag and
  HEADROOM_TOOL_PROFILES env var
- Integrate adaptive K into SmartCrusher, SearchCompressor, and
  LogCompressor, threading bias through ContentRouter
- Remove Grep and Bash from DEFAULT_EXCLUDE_TOOLS so their outputs
  are now compressed (only Read and Glob excluded)
2026-02-10 22:36:12 -08:00
angpt
0063fbdd13 Update anyllm.py
Using AnyLLM instead of acompletion
2026-02-06 13:15:23 -06:00
angpt
feeb72c0fc Adding any-llm as a backed provider 2026-02-06 11:40:23 -06:00
chopratejas
8f0754a622 Fix security vulnerabilities in memory and CCR systems
- Fix race condition in BatchContextStore.stats() by acquiring lock
- Add atomic dict snapshot in get_memory_stats() to prevent RuntimeError
- Add metadata key validation to prevent JSON path injection in SQLite
- Parameterize LIMIT/OFFSET in SQLite queries to prevent SQL injection
- Strengthen CCR hash validation to require exactly 24 hex characters
- Add comprehensive security validation tests
2026-02-04 12:05:50 -08:00
chopratejas
c90cf39826 Add LiteLLM backend routing for OpenAI endpoint and Magika content detection
- Route /v1/chat/completions through configured LiteLLM backend (Bedrock, Azure,
  Databricks, etc.) instead of hardcoding to OpenAI API
- Add send_openai_message() method to Backend base class and LiteLLMBackend
- Add Databricks provider to PROVIDER_REGISTRY
- Add /serving-endpoints/{model}/invocations endpoint for Databricks CLI compatibility
- Integrate Magika ML-based content detection in ContentRouter for improved accuracy
- Fall back to regex-based detection when Magika is unavailable
2026-02-04 10:14:34 -08:00
chopratejas
5e3aa3880f Fix token savings metrics mismatch between pipeline and server
The server was using tokenizer.count_text() on message content only,
while the pipeline uses tokenizer.count_messages() for full message
structure. This caused the "saved X tokens" logs to show different
values (e.g., 2.5M vs 6.7M for the same compression).

Now the server uses result.tokens_before and result.tokens_after from
the pipeline when optimization succeeds, ensuring consistent metrics
between pipeline logs and server logs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:53:44 -08:00
chopratejas
1aa312b923 Fix inflated token savings stats from cache hits
Cache hits were re-reporting the original compression savings to metrics,
causing massive inflation. With 400 cache hits each re-reporting 471K
"saved" tokens, stats showed 188M tokens saved instead of actual ~300K.

Token savings should only be counted once when compression is first
applied, not on every subsequent cache hit.
2026-02-02 13:19:08 -08:00
chopratejas
b579ea6eb1 Use AWS API to dynamically fetch Bedrock inference profiles
Instead of hardcoding model mappings, now fetches available inference
profiles from AWS Bedrock API using list_inference_profiles(). This:

- Automatically gets correct region-specific profiles (us., eu., apac.)
- Handles model name normalization from various input formats
- Caches results per region to avoid repeated API calls
- Supports all Claude models available in the user's region

Requires boto3 and valid AWS credentials.
2026-02-02 13:04:14 -08:00
chopratejas
3095c73282 Fix MCP tests to work when MCP SDK is not installed
- Mock MCP SDK availability for install/status/lifecycle tests
- Skip MCP server initialization tests when SDK not available
- Skip negative test (test without SDK) when SDK is installed

Tests now pass both with and without MCP SDK installed.
2026-02-02 11:23:26 -08:00
chopratejas
fe2e30a7ef Add MCP CLI for Claude Code subscription users
- Add `headroom mcp install` to configure ~/.claude/mcp.json
- Add `headroom mcp uninstall` to remove configuration
- Add `headroom mcp status` to check setup
- Add `headroom mcp serve` for MCP server (called by Claude Code)
- Add `mcp` optional dependency in pyproject.toml
- Add docs/mcp.md with full documentation
- Add 22 integration tests for MCP CLI
- Fix mypy errors in trained_router.py (None check, type annotation)
- Update README with subscription user instructions

This enables CCR (Compress-Cache-Retrieve) for subscription users who
don't have API access. MCP is Claude's official extension mechanism
that works with subscriptions.

Usage:
  pip install "headroom-ai[mcp]"
  headroom mcp install
  headroom proxy  # Terminal 1
  claude          # Terminal 2
2026-02-02 11:05:24 -08:00
Tejas Chopra
d51ea5635c
Merge pull request #26 from prakersh/dev/prakersh/fix
fix: Handle BaseModelOutputWithPooling from SigLIP get_*_features()
2026-02-02 09:33:31 -08:00
Prakersh Maheshwari
f768f444af fix: Handle BaseModelOutputWithPooling from SigLIP get_*_features()
Some versions of transformers return BaseModelOutputWithPooling objects
instead of raw tensors from get_image_features() and get_text_features().
This caused an AttributeError when trying to call .norm() on the output.

Added _extract_tensor() helper that handles both cases:
- Returns tensor unchanged if already a tensor (backward compatible)
- Extracts pooler_output from BaseModelOutputWithPooling objects

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 22:35:51 +05:30
Tejas Chopra
beddc990bd
Merge pull request #25 from prakersh/fix/increase-request-body-size-limit
fix: Increase MAX_REQUEST_BODY_SIZE from 10MB to 100MB to support image-heavy requests
2026-02-02 08:51:12 -08:00
Prakersh Maheshwari
658dc6c06c test: Update LLMLingua default model test to match new default
The default model was changed from xlm-roberta-large (~1GB) to
bert-base-multilingual (~350MB) to reduce memory usage. Updated
the test to reflect this change.

Fixes failing CI test: test_llmlingua_compressor.py::test_default_values

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 20:38:02 +05:30
Prakersh Maheshwari
bbe113a5f5 fix: Increase MAX_REQUEST_BODY_SIZE from 10MB to 100MB to support image-heavy requests
Fixes issue where Claude Code requests with images (e.g., from Playwright
screenshots) were being rejected with "Request too large" error.

Root cause: Hardcoded 10MB limit was too small for base64-encoded images.
Solution: Increased limit to 100MB to accommodate image-heavy payloads.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 20:31:35 +05:30
chopratejas
7cf10675ea Add centralized ML model configuration
- Create headroom/models/config.py as single source of truth for all ML model defaults
- Support environment variable overrides (HEADROOM_SENTENCE_TRANSFORMER, etc.)
- Update all components to use ML_MODEL_DEFAULTS instead of hardcoded values
- Switch LLMLingua default to smaller bert-base model (~350MB vs 1GB)
- Total memory footprint reduced from ~1.6GB to ~980MB

Updated files:
- MLModelRegistry now resolves defaults from config
- EmbeddingScorer, LocalEmbedder, TrainedRouter use config
- All dataclass configs use field(default_factory=...) for consistency
- Tests updated to handle auto-selected vector backends
2026-02-01 23:47:42 -08:00
chopratejas
621445a984 Bump version to 0.3.1 2026-02-01 22:53:21 -08:00
chopratejas
2dfbe0fcb9 Fix: Include dashboard HTML templates in wheel package 2026-02-01 22:51:56 -08:00
chopratejas
be0ca4c752 Fix mypy error: add type annotation for SentenceTransformer return 2026-02-01 22:47:05 -08:00
chopratejas
0bd9ce5024 Fix test_router_is_available_with_models after MLModelRegistry refactor
Remove unnecessary module-level patches for AutoTokenizer and
AutoModelForSequenceClassification since models are now loaded via
MLModelRegistry. The _load_models mock is sufficient for this test.
2026-02-01 22:42:11 -08:00
chopratejas
f20148081c Add centralized MLModelRegistry to share ML model instances
Previously, SentenceTransformer was loaded up to 5 times in different
components, wasting ~1.5GB of memory. Now all ML models are shared via
MLModelRegistry:

- SentenceTransformer (text embeddings)
- SIGLIP (image embeddings)
- spaCy (NER)
- Technique router (image optimization)

Updated components to use the registry:
- headroom/relevance/embedding.py
- headroom/memory/adapters/embedders.py
- headroom/cache/dynamic_detector.py
- headroom/prediction/feature_extractor.py
- headroom/evals/metrics.py
- headroom/image/trained_router.py
2026-02-01 22:33:02 -08:00
chopratejas
67d7db87cc Fix test to expect VectorBackend.AUTO as default 2026-02-01 22:25:26 -08:00
chopratejas
eca5c94229 Add SQLite vector backend as default and fix HNSW test skipping
- Add VectorBackend.AUTO that prefers SQLITE_VEC if available, else HNSW
- Add SQLiteVectorIndex configuration options (vector_db_path, vector_cache_size_kb)
- Add update_embedding() method to SQLiteVectorIndex for VectorIndex protocol
- Update factory to create SQLiteVectorIndex when appropriate
- Skip HNSW-specific tests when hnswlib is not available (fixes CI on Python 3.12)
2026-02-01 22:12:45 -08:00
chopratejas
84ad47eba9 Add SQLiteVectorIndex using sqlite-vec for bounded vector search
A SQLite-based alternative to HNSWVectorIndex offering:
- True CRUD operations (real deletes, not marks)
- Bounded memory via SQLite page cache (default 8MB)
- Persistent storage by default
- Cosine similarity search
- Native integration potential with FTS5 for hybrid search

New files:
- headroom/memory/adapters/sqlite_vector.py: Implementation
- tests/test_sqlite_vector_index.py: 16 comprehensive tests

Dependencies:
- sqlite-vec added as optional dependency (pip install sqlite-vec)
- Requires Python built with loadable extension support

Key advantages over HNSWVectorIndex:
- No SIGILL crash risk (pure C, no AVX requirement)
- True deletes (not just marks, space actually reclaimed)
- Simpler persistence (SQLite handles it automatically)
- Consistent with SQLiteGraphStore (same technology stack)
2026-02-01 21:23:09 -08:00
chopratejas
b27c95bdac Add bounded memory support to HNSWVectorIndex with LRU eviction
HNSWVectorIndex now supports optional memory bounding:
- New max_entries parameter sets soft limit on number of entries
- When limit reached, lowest importance entries are evicted
- Eviction uses importance (ascending) then age (oldest first) ordering
- eviction_batch_size controls how many entries evicted at once

Changes:
- Add max_entries and eviction_batch_size parameters
- Add _evict_entries() method for importance-based eviction
- Update get_memory_stats() to report budget_bytes and evictions
- Update stats() to include eviction metrics
- Update save_index/load_index to persist eviction settings
- Add 5 new tests for eviction behavior
2026-02-01 21:12:06 -08:00
Tejas Chopra
f5fc371f07
Merge pull request #24 from chopratejas/feature/memory-observability
Add SQLiteGraphStore for bounded, persistent graph storage
2026-02-01 21:07:23 -08:00
chopratejas
f2ecf1d2d0 Add SQLiteGraphStore for bounded, persistent graph storage
Replace unbounded InMemoryGraphStore with SQLite-backed implementation:
- Persistent storage survives proxy restarts
- Memory bounded by configurable SQLite page cache (default 8MB)
- Same async interface as InMemoryGraphStore (drop-in replacement)
- LocalBackend now uses SQLiteGraphStore by default (graph_persist=True)

New files:
- headroom/memory/adapters/sqlite_graph.py: SQLite graph store implementation
- tests/test_sqlite_graph_store.py: 37 comprehensive tests

Key features:
- O(log n) lookups via database indexes
- Case-insensitive entity name lookup per user
- BFS subgraph traversal and shortest path finding
- CASCADE delete for entity relationships
- MemoryTracker integration via get_memory_stats()
2026-02-01 20:48:57 -08:00
Tejas Chopra
8847355c57
Merge pull request #23 from chopratejas/feature/memory-observability
Add memory observability system (Phase 1)
2026-02-01 20:04:55 -08:00
chopratejas
e16691dd38 Add memory observability system (Phase 1)
Implements comprehensive memory tracking for all in-memory components:

- Add MemoryTracker singleton with ComponentStats, ProcessStats, MemoryReport
- Add get_memory_stats() to CompressionStore, BatchContextStore,
  GraphStore, HNSWVectorIndex
- Add /debug/memory API endpoint for runtime monitoring

Components tracked:
- compression_store: CCR compressed tool outputs
- batch_context_store: Batch API request contexts
- graph_store: Knowledge graph entities and relationships
- vector_index: HNSW vector embeddings
- semantic_cache: Response cache
- request_logger: Request metadata

Includes 47 tests (unit + integration) with real API calls.
2026-02-01 19:49:53 -08:00
chopratejas
5e2186c42a Add multi-provider memory system with auto-detection
- Add MemoryToolAdapter for unified memory across providers
- Anthropic: Uses native memory tool (memory_20250818) for subscription safety
- OpenAI/Gemini/Others: Uses function calling format
- All providers share the same semantic vector store backend
- Simplify CLI to single --memory flag with auto-detection
- Add proper resource cleanup (close methods) to fix test isolation
- Update README with memory documentation
2026-02-01 14:42:50 -08:00
Tejas Chopra
0e04099ea5
Merge pull request #20 from chopratejas/fix/intelligent-context-anthropic-format
fix: Extend Anthropic format tool protection to IntelligentContextManager
2026-02-01 12:31:47 -08:00
chopratejas
d7d50fef60 fix: Extend Anthropic format tool protection to IntelligentContextManager
Follows up on PR #19 which fixed RollingWindow but missed IntelligentContextManager,
the default context manager used by the proxy.

Changes:
1. intelligent_context.py: Extended `_get_protected_indices()` to handle Anthropic format:
   - Scan assistant.content for type="tool_use" blocks
   - Protect user messages containing type="tool_result" blocks with matching tool_use_id

2. test_intelligent_context.py: Added TestAnthropicFormatToolProtection class with 5 tests:
   - test_anthropic_tool_result_protected_when_tool_use_protected
   - test_anthropic_tool_units_dropped_atomically
   - test_anthropic_multiple_tools_same_message_atomic
   - test_anthropic_format_no_api_error_scenario (verifies bug fix)
   - test_mixed_openai_and_anthropic_formats

3. test_rolling_window.py: Added matching TestAnthropicFormatToolProtection class with 5 tests
   - Added skip decorator for CI/CD when OPENAI_API_KEY is not set

This ensures both context managers (RollingWindow and IntelligentContextManager) correctly
handle Anthropic's native tool_use/tool_result format, preventing the
"unexpected tool_use_id found in tool_result blocks" API error.
2026-02-01 12:27:17 -08:00
Tejas Chopra
adb710bfdc
Merge pull request #19 from prakersh/dev/prakersh/fix-anthropic-tool-unit-detection
fix: Handle Anthropic format tool_use/tool_result as atomic units
2026-02-01 12:20:38 -08:00
Prakersh Maheshwari
f83f92a51b fix: Handle Anthropic format tool_use/tool_result as atomic units
Root Cause:
The `find_tool_units()` function in `parser.py` only detected OpenAI
format tool calls (assistant.tool_calls + role="tool" messages), not
Anthropic format (assistant.content[type=tool_use] + user.content[type=tool_result]).

This caused RollingWindow and IntelligentContext transforms to treat
Anthropic tool_use and tool_result as separate, independently droppable
messages. When context needed to be trimmed, the assistant message with
tool_use could be dropped while keeping the user message with tool_result,
creating orphaned tool_result blocks.

When sent to the Anthropic API, this produces the error:
"unexpected tool_use_id found in tool_result blocks"

Changes:
1. parser.py: Extended `find_tool_units()` to detect Anthropic format:
   - Scan user messages for content blocks with type="tool_result"
   - Scan assistant messages for content blocks with type="tool_use"
   - Map tool_use_id to corresponding response message indices

2. rolling_window.py: Extended `_get_protected_indices()` to protect
   Anthropic format tool pairs:
   - Detect tool_use blocks in assistant.content
   - Find and protect matching user messages with tool_result blocks

3. tests/test_parser.py: Added 4 new tests for Anthropic format:
   - test_anthropic_format_tool_use_and_result
   - test_anthropic_format_multiple_tool_uses
   - test_anthropic_format_orphaned_tool_result
   - test_mixed_openai_and_anthropic_formats

Test Results: 82 passed (including 4 new Anthropic format tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 00:26:25 +05:30
chopratejas
4903bc957c Merge remote-tracking branch 'origin/main' 2026-01-31 20:17:57 -08:00
chopratejas
02e742f357 Add question-aware compression and enable ContentRouter by default
- Add `question` parameter to LLMLinguaCompressor.compress() for QA-aware
  token selection (passes to LLMLingua-2's compress_prompt)
- Flow `question` parameter through ContentRouter compression pipeline
- Enable ContentRouter in default pipeline (was missing, causing 0% compression)
- Add `content_router_enabled` config option to HeadroomConfig

This improves compression accuracy for QA tasks by allowing LLMLingua-2 to
preserve tokens relevant to answering the given question.
2026-01-31 20:16:51 -08:00
Tejas Chopra
c925352076
Merge pull request #18 from smartwatermelon/feature/exclude-tools-compression
feat(compression): add exclude_tools to bypass compression for specific tools
2026-01-31 18:29:14 -08:00
Claude Code Bot
dae51a1bb1 fix(config): remove web tools from default exclusion list
WebFetch and WebSearch should NOT be excluded by default because:
1. Web content is Headroom's sweet spot - lots of noise (nav, ads, boilerplate)
2. CCR allows retrieval if LLM needs original content
3. Excluding them undermines the core value proposition

DEFAULT_EXCLUDE_TOOLS now only contains local file/code tools:
- Read, Glob, Grep, Bash (and lowercase variants)

These local tools return precise content (line numbers, paths, code)
where exact fidelity matters immediately. Web tools benefit from
compression and can use CCR for on-demand retrieval.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 18:03:42 -08:00
Claude Code Bot
1c5a0e09fa fix(tests): add missing skip decorator and tests for exclude_tools
## What this PR fixes

1. **CI Python 3.12 failure**: Added skip decorator to `TestLocalBackend`
   in `test_memory_system.py` - these tests require hnswlib which is not
   available on all CI runners.

2. **Missing test coverage**: Added 6 tests for the `exclude_tools` feature
   in `test_content_router.py`. Tests use existing helper functions
   `generate_python_code()`, `generate_json_data()`, and
   `generate_search_results()` defined at lines 57-95 of the same file.

3. **Anthropic/OpenAI inconsistency**: Fixed `_process_content_blocks()`
   to add `router:excluded:tool` marker for Anthropic format, matching
   the OpenAI format behavior at line 1157.

4. **Dead code removal**: Removed unused `exclude_tools` field from
   `SmartCrusherConfig` - the actual implementation uses
   `ContentRouterConfig.exclude_tools` in content_router.py.

AI review: code-reviewer (2 iterations), adversarial-reviewer (2 iterations)
Issues fixed: missing test coverage, format inconsistency, dead code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 17:44:03 -08:00
Claude Code Bot
b6b8eed3bd fix(tests): skip memory tests when hnswlib not available
Add pytestmark skip conditions to memory test modules that depend on
hnswlib (core_operations, factory, easy). The subprocess probe for
hnswlib correctly detects unavailability on some platforms (like
Python 3.13 CI runners), but these tests were still trying to run
and failing with ImportError.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:09:53 -08:00
Claude Code Bot
c3f0327f03 fix(memory): use subprocess probe to avoid hnswlib SIGILL crash on CI
The previous fix attempted to lazily import hnswlib but calling
_check_hnswlib_available() still triggered the import, which crashed
with SIGILL on CPUs without AVX support before Python could catch it.

Fix by using subprocess to safely probe for hnswlib availability:
- Import AND create an Index in a subprocess to catch SIGILL at both
  import time and first use of AVX instructions
- If subprocess succeeds, then import in main process
- Add debug logging for all failure modes (timeout, crash, etc.)
- Isolates any crash to the subprocess, keeping test process alive

AI review: code-reviewer (1 iteration)
Adversarial review: code-critic (addressed logging, more robust probe)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:57:57 -08:00
Claude Code Bot
9ff67fbd1b fix(tests): skip HTML extractor tests when trafilatura not installed
Add pytest.importorskip("trafilatura") to HTML extractor test modules
to skip tests gracefully when the optional trafilatura dependency is
not installed. This fixes CI failures in the base test matrix that
doesn't include the html extras.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:39:55 -08:00
Claude Code Bot
e86d9a396e feat(compression): add exclude_tools to bypass compression for specific tools
Add ability to exclude specific tools from compression, useful for CLI tools
like Claude Code where file/search output should be passed through unmodified.

Changes:
- Add DEFAULT_EXCLUDE_TOOLS constant with Read, Grep, Glob, Bash, WebFetch, WebSearch
- Add exclude_tools field to SmartCrusherConfig and ContentRouterConfig
- Add _build_tool_name_map() to ContentRouter for tool_call_id -> name mapping
- Skip compression for tool_result blocks from excluded tools
- Support both Anthropic (tool_use/tool_result) and OpenAI (tool_calls/tool) formats

This prevents Headroom from compressing output from tools where the user
expects to see the full, unmodified content (e.g., file reads, search results).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 15:12:03 -08:00
chopratejas
8d1731b20c Add OpenRouter backend support with provider registry pattern
- Add native support for OpenRouter API via LiteLLM backend
- Introduce PROVIDER_REGISTRY pattern to eliminate scattered if/else blocks
- New providers can now be added with a single registry entry

Features:
- `headroom proxy --backend openrouter` routes requests to OpenRouter
- Pass-through model naming (anthropic/claude-3.5-sonnet, openai/gpt-4o, etc.)
- CLI shows provider-specific setup instructions from registry

Usage:
  export OPENROUTER_API_KEY="sk-or-v1-..."
  headroom proxy --backend openrouter

Also fixes mypy type errors in mcp_server.py
2026-01-31 14:58:09 -08:00
chopratejas
bbbf0c5a7e Fix mypy errors in html_extraction.py
Rename client/response variables to be unique per provider branch
to avoid type inference conflicts. Use getattr for Anthropic content
block text access to handle union types.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 10:16:41 -08:00