Spaces:
Build error
Build error
Merge pull request #60 from gglucass/feat/add-headroom-sdk
Browse filesfeat: allow overriding proxy telemetry sdk via HEADROOM_SDK
- CHANGELOG.md +3 -0
- docs/proxy.md +2 -0
- headroom/proxy/server.py +1 -1
- tests/test_proxy_telemetry_env.py +58 -0
CHANGELOG.md
CHANGED
|
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
| 8 |
## [Unreleased]
|
| 9 |
|
| 10 |
### Added
|
|
|
|
|
|
|
|
|
|
| 11 |
- **`headroom learn`** — Offline failure learning for coding agents
|
| 12 |
- Analyzes past conversation history (Claude Code, extensible to Cursor/Codex)
|
| 13 |
- **Success correlation**: for each failure, finds what succeeded after and extracts the specific correction
|
|
|
|
| 8 |
## [Unreleased]
|
| 9 |
|
| 10 |
### Added
|
| 11 |
+
- **Proxy telemetry SDK override** via `HEADROOM_SDK`
|
| 12 |
+
- Downstream apps can override the anonymous telemetry `sdk` field without patching installed files
|
| 13 |
+
- Blank values fall back to the default `proxy` label
|
| 14 |
- **`headroom learn`** — Offline failure learning for coding agents
|
| 15 |
- Analyzes past conversation history (Claude Code, extensible to Cursor/Codex)
|
| 16 |
- **Success correlation**: for each failure, finds what succeeded after and extracts the specific correction
|
docs/proxy.md
CHANGED
|
@@ -21,6 +21,8 @@ headroom proxy \
|
|
| 21 |
--budget 100.0
|
| 22 |
```
|
| 23 |
|
|
|
|
|
|
|
| 24 |
## Command Line Options
|
| 25 |
|
| 26 |
### Core Options
|
|
|
|
| 21 |
--budget 100.0
|
| 22 |
```
|
| 23 |
|
| 24 |
+
Telemetry is enabled by default. Opt out with `HEADROOM_TELEMETRY=off` or `headroom proxy --no-telemetry`. Downstream apps can set `HEADROOM_SDK=headroom-app` to override the anonymous telemetry `sdk` label; the default remains `proxy`.
|
| 25 |
+
|
| 26 |
## Command Line Options
|
| 27 |
|
| 28 |
### Core Options
|
headroom/proxy/server.py
CHANGED
|
@@ -7000,7 +7000,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
|
|
| 7000 |
|
| 7001 |
_beacon = TelemetryBeacon(
|
| 7002 |
port=config.port if hasattr(config, "port") else 8787,
|
| 7003 |
-
sdk="proxy",
|
| 7004 |
backend=config.backend if hasattr(config, "backend") else "anthropic",
|
| 7005 |
)
|
| 7006 |
|
|
|
|
| 7000 |
|
| 7001 |
_beacon = TelemetryBeacon(
|
| 7002 |
port=config.port if hasattr(config, "port") else 8787,
|
| 7003 |
+
sdk=os.environ.get("HEADROOM_SDK", "proxy").strip() or "proxy",
|
| 7004 |
backend=config.backend if hasattr(config, "backend") else "anthropic",
|
| 7005 |
)
|
| 7006 |
|
tests/test_proxy_telemetry_env.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for proxy telemetry environment variable handling."""
|
| 2 |
+
|
| 3 |
+
from unittest.mock import patch
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
pytest.importorskip("fastapi")
|
| 8 |
+
|
| 9 |
+
from headroom.proxy.server import ProxyConfig, create_app
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TestProxyTelemetrySDKEnv:
|
| 13 |
+
"""Test HEADROOM_SDK handling when the proxy builds telemetry beacons."""
|
| 14 |
+
|
| 15 |
+
def test_proxy_telemetry_sdk_defaults_to_proxy(self, monkeypatch):
|
| 16 |
+
"""Telemetry beacon uses the default SDK label when env var is unset."""
|
| 17 |
+
monkeypatch.delenv("HEADROOM_SDK", raising=False)
|
| 18 |
+
|
| 19 |
+
with patch("headroom.telemetry.beacon.TelemetryBeacon") as mock_beacon:
|
| 20 |
+
create_app(
|
| 21 |
+
ProxyConfig(
|
| 22 |
+
cache_enabled=False,
|
| 23 |
+
rate_limit_enabled=False,
|
| 24 |
+
cost_tracking_enabled=False,
|
| 25 |
+
)
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
assert mock_beacon.call_args.kwargs["sdk"] == "proxy"
|
| 29 |
+
|
| 30 |
+
def test_proxy_telemetry_sdk_uses_env_override(self, monkeypatch):
|
| 31 |
+
"""Telemetry beacon uses HEADROOM_SDK when it is non-empty."""
|
| 32 |
+
monkeypatch.setenv("HEADROOM_SDK", "headroom-app")
|
| 33 |
+
|
| 34 |
+
with patch("headroom.telemetry.beacon.TelemetryBeacon") as mock_beacon:
|
| 35 |
+
create_app(
|
| 36 |
+
ProxyConfig(
|
| 37 |
+
cache_enabled=False,
|
| 38 |
+
rate_limit_enabled=False,
|
| 39 |
+
cost_tracking_enabled=False,
|
| 40 |
+
)
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
assert mock_beacon.call_args.kwargs["sdk"] == "headroom-app"
|
| 44 |
+
|
| 45 |
+
def test_proxy_telemetry_sdk_empty_env_falls_back_to_proxy(self, monkeypatch):
|
| 46 |
+
"""Telemetry beacon falls back to proxy when HEADROOM_SDK is blank."""
|
| 47 |
+
monkeypatch.setenv("HEADROOM_SDK", " ")
|
| 48 |
+
|
| 49 |
+
with patch("headroom.telemetry.beacon.TelemetryBeacon") as mock_beacon:
|
| 50 |
+
create_app(
|
| 51 |
+
ProxyConfig(
|
| 52 |
+
cache_enabled=False,
|
| 53 |
+
rate_limit_enabled=False,
|
| 54 |
+
cost_tracking_enabled=False,
|
| 55 |
+
)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
assert mock_beacon.call_args.kwargs["sdk"] == "proxy"
|