967b0db439
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.
193 lines
4.3 KiB
Python
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
|