Files
chopratejas 967b0db439 fix: B1 — retire ICM, RollingWindow, scoring, relevance + dependents
Phase B step 1 of the live-zone-only realignment. Removes ~10K LOC of
"drop messages from history" machinery that became unreachable after
PR-A1 made `/v1/messages` a passthrough on the proxy. Live-zone-only
compression (PR-B2..B7) operates on content blocks within messages;
message-list mutation no longer happens in the pipeline.

Python deletes:
- headroom/transforms/intelligent_context.py (1077 LOC)
- headroom/transforms/rolling_window.py (395 LOC)
- headroom/transforms/progressive_summarizer.py (508 LOC)
- headroom/transforms/scoring.py (459 LOC)
- headroom/transforms/tool_crusher.py (338 LOC)
- 5 corresponding tests/test_transforms/* and tests/test_proxy_intelligent_context.py

Rust deletes:
- crates/headroom-core/src/context/* (manager, config, workspace,
  candidate, ccr_drop, strategy/, mod) + safety.rs replaced
- crates/headroom-core/src/scoring/* (mod, score, scorer, traits, weights)
- MessageScorerComparator from crates/headroom-parity (PR #338/#343
  becomes deletable; sunk cost stays sunk)
- 13 message_scorer fixtures + record_message_scorer.py

Rust adds (move + rewrite):
- crates/headroom-core/src/transforms/safety.rs — `tool_pair_indices`
  preserves the OpenAI/Anthropic tool_use ↔ tool_result pairing rule
  the live-zone dispatcher (PR-B2) needs. No IcmConfig dependency.

Surface refactors:
- HeadroomConfig: drop `tool_crusher`, `rolling_window`,
  `intelligent_context` fields; hoist `output_buffer_tokens` to top
  level (used by client.py).
- ProxyConfig: drop `intelligent_context*` fields.
- `headroom wrap` proxy server: retire IntelligentContextManager
  and RollingWindow imports + branch; pipeline is CacheAligner →
  ContentRouter (smart_routing) or CacheAligner → SmartCrusher
  (legacy).
- CLI: drop `--no-intelligent-context`, `--no-intelligent-scoring`,
  `--no-compress-first` flags.
- LangChain memory integration: rename `_apply_rolling_window` →
  `_apply_compression`, drop RollingWindowConfig dep. Threshold is
  now advisory — B6 will rework the contract.
- TransformPipeline.create_pipeline now takes only cache_aligner_config.
- headroom/__init__.py + headroom/transforms/__init__.py: strip
  exports of deleted symbols.

Bug fixes uncovered by full pytest sweep:
- providers/copilot/wrap.py: `environ or os.environ` collapsed
  empty-dict to falsy → callers passing `environ={}` accidentally
  pulled from os.environ. Use `environ if environ is not None else
  os.environ`.

Test correctness fixes:
- _DummyAnthropicHandler._retry_request gains **_kwargs to match
  the real handler signature post-A8.
- test_ws_http_fallback extracts JSON from `content=` (post-A3
  byte-faithful) rather than the obsolete `json=` kwarg.
- test_ccr_response_handler_extra fixture joins SSE events with
  `\n\n` per spec (post-A8 byte-buffer parser requirement).
- test_proxy_responses_phase_preservation: capture via direct
  handler attached to the named logger, so the assertion is
  order-independent (proxy `_setup_file_logging` flips
  `headroom.propagate=False` once any earlier test triggers it).
- conftest.py autouse fixture resets `headroom.propagate=True`
  before each test as a defensive measure for the same pollution.
- test_wrap_copilot_translated_backend_still_requires_byok:
  monkeypatch.delenv every provider key so the BYOK error
  actually fires.
- test_native_installers: skip when system bash < 4.3 (macOS ships 3.2).
- TestGeminiEmbedContent / TestGeminiBatchEmbedContents:
  pytest.mark.skip — proxy currently has no :embedContent route;
  feature gap, not regression.

Acceptance:
- cargo build --workspace + cargo clippy + cargo fmt --check: green.
- cargo test --workspace --exclude headroom-py: 777 passed.
- pytest: 4892 passed, 240 skipped, 0 failed.
- git grep returns only intentional comments referencing the deletion.

Per-PR-B1 plan: REALIGNMENT/04-phase-B-live-zone.md.
2026-05-02 12:23:17 -07:00

193 lines
4.3 KiB
Python

"""Custom exceptions for Headroom.
This module provides explicit exception classes for better error handling
and debugging. All exceptions inherit from HeadroomError, making it easy
to catch all Headroom-related errors.
Example:
from headroom import HeadroomClient, HeadroomError, ConfigurationError
try:
client = HeadroomClient(...)
client.validate_setup()
except ConfigurationError as e:
print(f"Configuration problem: {e}")
except HeadroomError as e:
print(f"Headroom error: {e}")
"""
from __future__ import annotations
from typing import Any
class HeadroomError(Exception):
"""Base exception for all Headroom errors.
All Headroom exceptions inherit from this class, making it easy
to catch any Headroom-related error:
try:
client.chat.completions.create(...)
except HeadroomError as e:
# Handle any Headroom error
pass
"""
def __init__(self, message: str, details: dict[str, Any] | None = None):
super().__init__(message)
self.message = message
self.details = details or {}
def __str__(self) -> str:
if self.details:
detail_str = ", ".join(f"{k}={v}" for k, v in self.details.items())
return f"{self.message} ({detail_str})"
return self.message
class ConfigurationError(HeadroomError):
"""Raised when Headroom is misconfigured.
This includes:
- Invalid mode values
- Missing required configuration
- Incompatible configuration combinations
Example:
ConfigurationError(
"Invalid mode 'foo'",
details={"valid_modes": ["audit", "optimize"]}
)
"""
pass
class ProviderError(HeadroomError):
"""Raised when there's an issue with the LLM provider.
This includes:
- Provider not recognized
- Provider-specific configuration issues
- Token counter errors
Example:
ProviderError(
"Unknown provider",
details={"provider": "foo", "known_providers": ["openai", "anthropic"]}
)
"""
pass
class StorageError(HeadroomError):
"""Raised when there's an issue with metrics storage.
This includes:
- Database connection failures
- Invalid storage URL
- Write failures
Example:
StorageError(
"Cannot connect to database",
details={"url": "sqlite:///foo.db", "error": "Permission denied"}
)
"""
pass
class CompressionError(HeadroomError):
"""Raised when compression fails.
This includes:
- Parse errors in tool outputs
- Invalid JSON structures
- Compression strategy failures
Example:
CompressionError(
"Failed to parse tool output",
details={"tool_name": "search_api", "content_preview": "..."}
)
"""
pass
class TokenizationError(HeadroomError):
"""Raised when token counting fails.
This includes:
- Unknown model for tokenization
- Encoding errors
- Tiktoken/tokenizer loading failures
Example:
TokenizationError(
"Unknown model for tokenization",
details={"model": "gpt-99", "fallback_used": True}
)
"""
pass
class CacheError(HeadroomError):
"""Raised when caching operations fail.
This includes:
- Cache store errors
- Retrieval failures
- CCR (Compress-Cache-Retrieve) errors
Example:
CacheError(
"Cache entry expired",
details={"hash": "abc123", "ttl": 300}
)
"""
pass
class ValidationError(HeadroomError):
"""Raised when setup validation fails.
This is raised by validate_setup() when the configuration
or environment is not properly set up.
Example:
ValidationError(
"Setup validation failed",
details={
"provider_ok": True,
"storage_ok": False,
"storage_error": "Cannot write to database"
}
)
"""
pass
class TransformError(HeadroomError):
"""Raised when a transform fails to apply.
This includes:
- SmartCrusher failures
- ContentRouter errors
- Pipeline errors
Example:
TransformError(
"Transform failed",
details={"transform": "smart_crusher", "reason": "..."}
)
"""
pass