Commit graph

2630 commits

Author SHA1 Message Date
github-actions[bot]
01762b1ec7
chore: release main (#607)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-08 11:21:23 -07:00
Tejas Chopra
1c8c538780
fix: preserve Claude Code tool-search deferral through the proxy (#746) (#753)
Routing Claude Code through the proxy disabled its on-demand tool loading:
with a custom ANTHROPIC_BASE_URL and ENABLE_TOOL_SEARCH unset, Claude Code
stops deferring MCP/system tool schemas behind the server-side Tool Search
Tool and materializes them all into local context (~25K tokens) — the
opposite of what a context-optimization proxy should do.

Root cause is a client-side gate in Claude Code (isToolSearchEnabledOptimistic):
deferral is disabled when ENABLE_TOOL_SEARCH is unset AND provider is
first-party AND the base-URL host is not api.anthropic.com. It is a one-way
URL check, not a capability handshake, so no proxy/response header can flip
it. The only lever is the ENABLE_TOOL_SEARCH env var Claude Code reads at
startup.

Changes:
- wrap claude: inject ENABLE_TOOL_SEARCH into the launched Claude Code env
  (default "true"; --tool-search true|auto|auto:N|false; a pre-set env value
  is respected; blank is treated as unset). Keeps deferral on through the proxy.
- proxy: emit a one-time, actionable hint when a Claude Code request is
  detected loading tools eagerly (for users who run `claude` manually). Gated
  on a cheap one-shot flag and wrapped so it can never fail a request.
- docs: troubleshooting section with before/after verification.
- tests: 30 unit tests (value validation, injection precedence, detection,
  hint content, one-shot guard).
2026-06-08 11:20:48 -07:00
Tejas Chopra
8f6a9cfb26 fix: sort anthropic handler imports 2026-06-08 01:15:13 -07:00
Vipin Singh
8dbe7ad41b
feat(proxy): show resolved upstream API targets in startup banner (#586)
Display the resolved upstream API targets (Anthropic, OpenAI, Gemini,
Cloud Code) in the proxy startup banner so users can verify their
custom endpoint configuration at a glance.

The new UPSTREAM TARGETS section appears between the Backend line and
the FEATURES section. URLs are resolved through the existing
resolve_api_targets pipeline, which normalizes trailing /v1 suffixes
and applies defaults for unconfigured providers.

Closes #583

Co-authored-by: vipin-si <vipin-si@users.noreply.github.com>
2026-06-08 00:19:20 -07:00
Patrick A
176d4c772a
fix(startup): move HF/httpx log suppression before sentence_transformers init (#622)
The existing suppression in headroom/memory/adapters/embedders.py fires at
module-import time for that file, but sentence_transformers is imported lazily
(only when SentenceTransformer() is instantiated in a worker process).  This
means the log levels are set too late: httpx and huggingface_hub have already
emitted their INFO/WARNING records by the time embedders.py is first imported.

Fix: move the suppression to headroom/cli/proxy.py module level.  This file is
imported during CLI registration (_register_commands in main.py), well before
any worker forks or ML initialisation.  Setting log levels here guarantees they
are in place for every code path that eventually loads sentence_transformers.

Changes:
- logging.getLogger("httpx").setLevel(WARNING) -- suppress manifest HEAD/GET INFO
- logging.getLogger("huggingface_hub").setLevel(ERROR)
- logging.getLogger("huggingface_hub.utils._http").setLevel(ERROR)
- logging.getLogger("sentence_transformers").setLevel(WARNING)
- os.environ.setdefault("HF_HUB_DISABLE_IMPLICIT_TOKEN", "1") -- pre-empt env check
- os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
- warnings.filterwarnings to suppress unauthenticated/HF_TOKEN UserWarnings

Eliminates ~50 noisy startup log lines with 8 workers (6 HEAD requests each).

Complementary to PR #619 which adds the same suppression in embedders.py;
this PR adds the MISSING earlier suppression point.

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
Co-authored-by: Devanshi Vyas <dnv2103@columbia.edu>
2026-06-08 00:15:39 -07:00
Patrick A
fa558c5647
fix(deps): move gunicorn to [proxy-prod] extra, add Windows guard (#537)
* fix(deps): add missing runtime deps to [code] and [proxy] extras

- Add gunicorn>=21.0.0 to the [proxy] extra

The proxy docs (docs/content/docs/proxy.mdx and wiki/proxy.md) show
gunicorn as the recommended production deployment server:

  pip install gunicorn
  gunicorn headroom.proxy.server:app --worker-class uvicorn.workers.UvicornWorker

Users installing headroom-ai[proxy] for production get uvicorn (already
declared) but had to discover and install gunicorn manually. Adding it
to [proxy] removes that friction.

Investigation notes:
- [code] only needs tree-sitter-language-pack (already declared).
  code_compressor.py has zero numpy imports. The kompress fallback
  inside code_compressor.py is guarded by ImportError and requires [ml].
- numpy is correctly declared in [relevance] (numpy>=1.24.0) and pulled
  transitively by sentence-transformers in [memory]. It is NOT needed
  under [code].
- tree-sitter is a transitive dep of tree-sitter-language-pack (requires
  tree-sitter>=0.25.2) so it does not need an explicit entry.

* docs(changelog): add entry for gunicorn proxy dep fix

style(tests): ruff format test_provider_proxy_routes.py (blank lines after docstrings)

* fix(deps): move gunicorn to [proxy-prod] extra, add Windows guard

- Remove gunicorn from [proxy] so dev, CI, and Windows users are not
  forced to install a Unix-only package that does nothing on Windows
- Add new [proxy-prod] extra that includes [proxy] + gunicorn with a
  sys_platform != 'win32' environment marker
- Production users: pip install 'headroom-ai[proxy,proxy-prod]'
- Update CHANGELOG to reflect the new extra name

* fix(devcontainer): bump uv floor to >=0.11.0 for lockfile compatibility

uv 0.6.17 (previously pinned) cannot parse lockfiles generated by
uv >= 0.11.x. The validate CI job (triggered by pyproject.toml
changes) was failing with 'Failed to parse uv.lock'. Loosening the
pin to >=0.11.0 picks up the matching format parser while keeping the
Docker layer cacheable with a range rather than an exact pin.

* fix(devcontainer): skip gitpython wheel filename check in uv sync

gitpython 3.1.47 on PyPI has wheel gitpython-3.1.46-py3-none-any.whl
(wrong filename). uv >=0.11.19 strict filename validation rejects this
lockfile entry. UV_SKIP_WHEEL_FILENAME_CHECK=1 bypasses the check until
the upstream lockfile is regenerated with a corrected entry.

* fix(deps): correct gitpython version in uv.lock to match actual wheel

gitpython 3.1.47 on PyPI was uploaded with sdist/wheel files named
gitpython-3.1.46.*. The version field in uv.lock said 3.1.47 but all
download URLs reference 3.1.46 files, causing uv >=0.11.19 to refuse
to parse the lockfile with a version-mismatch error.

Change the version field to 3.1.46 so the entry is internally
consistent. Also revert the now-unnecessary UV_SKIP_WHEEL_FILENAME_CHECK
workaround from post-create.sh.

---------

Co-authored-by: Tejas Chopra <chopratejas@gmail.com>
2026-06-07 23:49:14 -07:00
Tejas Chopra
827461c593 Merge remote-tracking branch 'origin/main' into realign-F4-trust-forwarded-only-gateway
# Conflicts:
#	headroom/proxy/handlers/anthropic.py
2026-06-07 22:17:15 -07:00
dependabot[bot]
cbba035386
ci: bump docker/setup-buildx-action from 3 to 4 (#165)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-07 16:37:53 -07:00
yehsuf
d90cdce3b6
feat: support CLAUDE_CODE_USE_FOUNDRY and custom upstream gateways (#726)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 15:27:44 -07:00
Kumario
9fe4886cf6
feat(perf): add --format {text,json,csv} to headroom perf (#648)
* feat(perf): add structured summary/record builders to analyzer

parse_log_files() already returns a fully-structured PerfReport, but
the only way to read it was the colored text report. Add reusable
machine-readable views so CI guards, dashboards, and agent harnesses
can consume perf data without scraping ANSI text:

- build_perf_summary(report) -> dict with the aggregated KPIs
  (savings_pct, cache_hit_pct, by_model, by_transform, ...), mirroring
  format_report() numbers exactly.
- perf_records_as_dicts(report) -> per-record list for --raw output.
- PERF_RECORD_FIELDS: shared column order for CSV/raw consumers.

Pure additions; no behaviour change to existing callers. Part of #595.

* feat(perf): add --format {text,json,csv} to headroom perf

Adds a machine-readable output path to the perf command (issue #595):

- --format json: aggregated summary (default) or, with --raw, a JSON
  array of per-record dicts.
- --format csv: per-model breakdown (default) or, with --raw, one row
  per PERF record using the shared PERF_RECORD_FIELDS column order.
- --format text (default): unchanged human-readable report.

Enables CI guards (jq '.savings_pct < 70'), dashboards, and agent
wrappers to consume perf data without scraping ANSI text.

Closes #595.

* test(perf): cover --format json/csv and structured builders

Unit tests for build_perf_summary (totals, savings/cache pct,
by_model/by_transform, empty-report zero-division guard) and
perf_records_as_dicts, plus CliRunner integration tests for
--format json, json --raw, csv, csv --raw, the unchanged text
default, and rejection of an unknown format. Part of #595.

* fix(perf): rename transform loop var to satisfy mypy

The structured-summary builder reused `recs` for both the per-model
(list[PerfRecord]) and per-transform (list[TransformRecord]) groupings, so
mypy flagged the second assignment as an incompatible-type reuse
(analyzer.py:704). Rename the transform loop variable to `t_recs` so each
loop keeps a single element type. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Kumario1 <ramsakal.ipec@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:43:10 -07:00
Devanshi Vyas
fb59f83fab
Merge pull request #592 from divyanshus2404/my-first-contribution
docs: add troubleshooting section
2026-06-06 12:08:07 -07:00
Divyanshu Singh
67f005e434 Address PR feedback: Move troubleshooting, refine rust docs, and update install options 2026-06-07 00:27:09 +05:30
Devanshi Vyas
e81c96ae1a
Merge pull request #699 from oxura/docs/claude-mcp-usage-notes
docs: explain Claude MCP usage attribution
2026-06-06 09:15:47 -07:00
oxura
10da89d3ce docs: explain Claude MCP usage attribution 2026-06-06 19:50:24 +06:00
Devanshi Vyas
26f325f57b
Merge pull request #652 from chopratejas/contributing-md
docs: update contributing.md
2026-06-05 22:05:53 -07:00
Devanshi Vyas
264271b595 docs: update contributing.md 2026-06-05 22:02:28 -07:00
Devanshi Vyas
cd52556d08
docs: add star history in readme 2026-06-05 16:49:46 -07:00
Devanshi Vyas
16358b64ca
Merge pull request #642 from RTCartist/docs/windows-install-prereqs
docs(install): document Windows build prerequisites
2026-06-05 16:16:12 -07:00
Patrick A
45559011b1
fix(startup): suppress proxy startup log noise (#619)
* docs: add enterprise.md

* docs: add link to enterprisemd in README

* fix(copilot): restore generic endpoint for non-subscription OAuth (#610) (#612)

* fix(copilot): restore generic endpoint for non-subscription OAuth (#610)

0.23.0 re-pointed the shared Copilot OAuth branch from the generic
api.githubcopilot.com host to the account-specific endpoints.api host
returned by /copilot_internal/user, and made resolve_copilot_api_url
ignore the GITHUB_COPILOT_API_URL override whenever a token resolved.

That change was meant to add --subscription, but it also altered the
pre-existing non-subscription OAuth flow that worked on 0.22.4. The
account host does not serve newer models (e.g. gpt-5.4) on the responses
API, so wrapped requests began failing with unsupported-model errors
while plain Copilot and 0.22.4 kept working.

Restore 0.22.4 routing for non-subscription OAuth (generic host, still
overridable) and keep account resolution only for --subscription.
resolve_copilot_api_url now honors GITHUB_COPILOT_API_URL first, so the
override escape hatch works for every path. BYOK is unaffected.

Add a regression suite that mocks a successful user-info response, the
real-world path the prior test never exercised (it relied on the network
call failing in CI and falling back to the generic host).

* fix(copilot): route subscription + OAuth through the generic host (#610)

The 0.23.0 endpoint resolution derived the Copilot API host from
/copilot_internal/user (endpoints.api), which returns a segmented host
(e.g. api.individual.githubcopilot.com) that does not serve newer models
on the responses API and is not the host the official Copilot client
routes with (that comes from the token-exchange endpoint). --subscription
used the identical resolution, so it carried the same latent regression
as the non-subscription OAuth path.

Make Copilot host resolution override -> generic for BOTH --subscription
and the implicit OAuth path, and stop using user-info to route. Accounts
that require a dedicated host (enterprise / data residency) pin it via
GITHUB_COPILOT_API_URL. resolve_copilot_api_url no longer makes a network
call; _fetch_copilot_user_info is retained for token validation.

Update the subscription smoke tests that encoded the old account-host
assumption, and add wrap-level + unit coverage that --subscription routes
to the generic host even when user-info advertises an account host, and
that the GITHUB_COPILOT_API_URL override flows through both paths.

* docs(copilot): document generic-host routing + enterprise override (#610)

Spell out the routing contract introduced by the #610 fix so enterprise
users have a supported path. Headroom routes wrapped Copilot hosted
traffic (--subscription and OAuth) to the generic api.githubcopilot.com,
and accounts on a dedicated host (Enterprise Cloud data residency, egress
proxy) pin it via GITHUB_COPILOT_API_URL.

- copilot --help: note the generic host + GITHUB_COPILOT_API_URL override.
- TESTING-copilot-subscription.md: add "API host & Enterprise / data
  residency" section; correct the stale api.*.githubcopilot.com claim; and
  invite enterprise tenants who want token-exchange-based auto-detection to
  open an issue.
- integration-guide.md: short hosted-host + override note in the Copilot
  section.

* fix(wrap): report unbindable proxy ports (#602)

* fix(proxy): fail-open on corrupt golden bytes instead of RuntimeError (#603)

* fix(proxy): fail-open on corrupt golden bytes instead of RuntimeError

Permanent session corruption: once golden bytes become unreadable
(UnicodeDecodeError / JSONDecodeError), every subsequent request for
the session raised RuntimeError, returning 500 until proxy restart.

Fix: log at ERROR level and recover — skip the corrupt memory tool,
or regenerate a fresh CCR definition — rather than propagating
RuntimeError and permanently breaking the session.

Also change proxy_inbound_request_aborted from logger.info to
logger.error with exc_info=True so tracebacks appear in logs.

Closes: proxy silent-500 sessions in the wild (observed 2026-06-04)

* fix(tests): re-enable headroom log propagation in corrupt-bytes tests

configure_proxy_logging() sets headroom_logger.propagate = False to prevent
duplicate writes when the proxy redirects stderr to a log file. In CI the
proxy initialises its logging stack before the test suite, leaving propagation
disabled. pytest's caplog handler attaches to the root logger, so records that
stop at the headroom logger are never captured.

Added _enable_headroom_log_propagation autouse fixture that temporarily
re-enables propagation for the duration of each test, making caplog capture
work regardless of the surrounding logging configuration.

* fix(tests): remove unused imports from corrupt-bytes regression tests

Remove json, SessionCcrTracker, and SessionToolTracker imports that were
imported but never referenced in the test body. Fixes ruff F401 and I001
lint errors reported by CI.

---------

Co-authored-by: Patrick Ancillotti <patrick.ancillotti@people.inc>

* fix(startup): suppress log noise from litellm, trafilatura, HF hub, and tiktoken warning

* fix(startup): suppress httpx INFO logs from sentence_transformers HEAD checks

* docs(changelog): add entry for startup log noise suppression fixes

* refactor(startup): extract hf_hub_download_local_first into onnx_runtime

The three _hub_download/_hub_dl helpers in embedders.py, onnx_router.py, and
kompress_compressor.py are identical -- try local cache first, fall back to
network download. Extract into a single hf_hub_download_local_first() function
in onnx_runtime.py (the natural home for shared ORT/HF utilities) and update
all three callers to use it.

* fix(lint): sort imports and remove unused _FALLBACK_WARNING_SHOWN import

* fix(lint): cast hf_hub_download return to str for mypy no-any-return

---------

Co-authored-by: Patrick Ancillotti <patrick.ancillotti@people.inc>
2026-06-05 14:44:40 -08:00
Patrick A
53d2342291
ci: speed up GitHub Actions — path filters, caching, timeouts, version upgrades (#620)
* ci: speed up GitHub Actions - path filters, caching, timeouts, version upgrades

Performance improvements:
- init-e2e.yml, wrap-e2e.yml: add path filters so e2e Docker builds only run when
  e2e-related files change (saves ~10 min per irrelevant PR push)
- init-e2e.yml, wrap-e2e.yml: add concurrency groups to cancel superseded PR runs
- ci.yml: add pip caching to lint and build jobs
- ci.yml: cache actionlint + act binaries in workflow-validation (skip curl on hits)
- eval.yml: add pip caching to smoke-test and weekly-suite jobs
- docs.yml: add pip caching for mkdocs-material install
- rust.yml: replace cargo install --locked cargo-audit/deny with taiki-e/install-action
  (prebuilt binaries; saves 2-5 min per audit run)

Bug fixes:
- docker.yml: fix actions/checkout@v6 -> @v4 (v6 does not exist; would break all
  Docker builds on every release/PR touching docker paths)

Version upgrades:
- wagoid/commitlint-github-action: @v5 -> @v6
- devcontainers.yml: docker/setup-buildx-action@v3 -> @v4 (align with docker.yml)

Safety improvements:
- ci.yml: add timeout-minutes to all 13 jobs (changes, lint, build-wheel,
  prefetch-model, test x4, test-extras, test-agno, commitlint, build,
  workflow-validation, docker-native-e2e, windows-native-wrapper, macos-native-wrapper)
- docker.yml: add timeout-minutes to docker-build (75m), docker-manifest (20m),
  promote-latest (10m)
- eval.yml: add timeout-minutes to smoke-test (30m); bump weekly-suite 60->90m
- rust.yml: add timeout-minutes to test (30m), wheels (45m), audit (20m)

Observed wall-clock impact on recent PRs:
- Init E2E and Wrap E2E were running on every single PR push regardless of content
- CI workflow was taking 12-17 min; path filters reduce unnecessary e2e runs to 0

* fix(ci): bust actionlint+act cache when workflow file changes

Static cache key 'ci-tools-actionlint-act-v1' never invalidated on
tool version updates. Switched to hashFiles('.github/workflows/ci.yml')
so the cache busts automatically whenever the download scripts are
updated to point at a newer release.

Flagged by adversarial review (Architecture + Testing/Reliability personas).

* fix(ci): add missing Dockerfile COPY paths to e2e path filters

e2e/init/Dockerfile and e2e/wrap/Dockerfile COPY files not covered
by the initial path filter set:

  init-e2e: Cargo.toml, Cargo.lock, rust-toolchain.toml, uv.lock,
            .claude-plugin, .github/plugin/**, plugins/headroom-agent-hooks/**
  wrap-e2e: Cargo.toml, Cargo.lock, rust-toolchain.toml, uv.lock,
            sdk/typescript/**, plugins/openclaw/**

Without these, a Rust toolchain bump or SDK change on a PR would
skip the e2e gate entirely, only catching it on the merge to main.

Flagged by adversarial review (Domain/Correctness persona).

* fix(devcontainer): upgrade uv to >=0.7.0 to parse uv.lock revision=3

* fix(devcontainer): set UV_SKIP_WHEEL_FILENAME_CHECK=1 in post-create.sh for gitpython wheel

* ci: bump actions/checkout and actions/setup-node to v5 (Node.js 20 EOL Jun 16)

* fix(devcontainer): export UV_SKIP_WHEEL_FILENAME_CHECK so uv run also skips wheel check

* ci: bump all GitHub Actions to latest versions (Node.js 24)

* fix(test): accept release-please-action v4 or v5 in workflow assertion

* fix(format): ruff format test_release_workflows.py
2026-06-05 14:32:53 -08:00
Mansur Japarov
30c1ac8656
fix(proxy): route Claude Code model metadata to Anthropic (#627) 2026-06-05 14:28:17 -08:00
Leoy
88177bd7a6
feat(relevance): weight BM25 score_batch by corpus IDF (#646)
BM25Scorer.score_batch() ranks a real corpus of documents but weighted
every matched term with a constant idf=log(2.0), so a ubiquitous noise
word counted the same as a discriminative UUID. The _compute_idf() helper
needed to do this properly already existed (and was unit-tested) but was
never wired into scoring.

- Implement _compute_idf() with the standard floored BM25 IDF its docstring
  documents: log((N - n + 0.5) / (n + 0.5) + 1).
- Thread an optional per-term idf_map through _bm25_score(); single-document
  score() keeps the neutral log(2.0) weight (no corpus to estimate from).
- score_batch() now computes document frequency across the batch and builds
  the IDF map, so rare/discriminative terms outrank corpus-wide terms in the
  ranking that CompressionStore.search() and HybridScorer consume.

Adds tests covering the IDF formula and the batch ranking behaviour.
2026-06-05 14:15:44 -08:00
RTCartist
794916bf1a docs(install): document Windows build prerequisites
Addresses #636.
2026-06-05 22:35:31 +08:00
Patrick A
2170a1b4a0
fix(proxy): fail-open on corrupt golden bytes instead of RuntimeError (#603)
* fix(proxy): fail-open on corrupt golden bytes instead of RuntimeError

Permanent session corruption: once golden bytes become unreadable
(UnicodeDecodeError / JSONDecodeError), every subsequent request for
the session raised RuntimeError, returning 500 until proxy restart.

Fix: log at ERROR level and recover — skip the corrupt memory tool,
or regenerate a fresh CCR definition — rather than propagating
RuntimeError and permanently breaking the session.

Also change proxy_inbound_request_aborted from logger.info to
logger.error with exc_info=True so tracebacks appear in logs.

Closes: proxy silent-500 sessions in the wild (observed 2026-06-04)

* fix(tests): re-enable headroom log propagation in corrupt-bytes tests

configure_proxy_logging() sets headroom_logger.propagate = False to prevent
duplicate writes when the proxy redirects stderr to a log file. In CI the
proxy initialises its logging stack before the test suite, leaving propagation
disabled. pytest's caplog handler attaches to the root logger, so records that
stop at the headroom logger are never captured.

Added _enable_headroom_log_propagation autouse fixture that temporarily
re-enables propagation for the duration of each test, making caplog capture
work regardless of the surrounding logging configuration.

* fix(tests): remove unused imports from corrupt-bytes regression tests

Remove json, SessionCcrTracker, and SessionToolTracker imports that were
imported but never referenced in the test body. Fixes ruff F401 and I001
lint errors reported by CI.

---------

Co-authored-by: Patrick Ancillotti <patrick.ancillotti@people.inc>
2026-06-04 18:19:06 -07:00
oxura
6dfcaa839f
fix(wrap): report unbindable proxy ports (#602) 2026-06-04 18:19:00 -07:00
Tejas Chopra
18925b8c6e
fix(copilot): restore generic endpoint for non-subscription OAuth (#610) (#612)
* fix(copilot): restore generic endpoint for non-subscription OAuth (#610)

0.23.0 re-pointed the shared Copilot OAuth branch from the generic
api.githubcopilot.com host to the account-specific endpoints.api host
returned by /copilot_internal/user, and made resolve_copilot_api_url
ignore the GITHUB_COPILOT_API_URL override whenever a token resolved.

That change was meant to add --subscription, but it also altered the
pre-existing non-subscription OAuth flow that worked on 0.22.4. The
account host does not serve newer models (e.g. gpt-5.4) on the responses
API, so wrapped requests began failing with unsupported-model errors
while plain Copilot and 0.22.4 kept working.

Restore 0.22.4 routing for non-subscription OAuth (generic host, still
overridable) and keep account resolution only for --subscription.
resolve_copilot_api_url now honors GITHUB_COPILOT_API_URL first, so the
override escape hatch works for every path. BYOK is unaffected.

Add a regression suite that mocks a successful user-info response, the
real-world path the prior test never exercised (it relied on the network
call failing in CI and falling back to the generic host).

* fix(copilot): route subscription + OAuth through the generic host (#610)

The 0.23.0 endpoint resolution derived the Copilot API host from
/copilot_internal/user (endpoints.api), which returns a segmented host
(e.g. api.individual.githubcopilot.com) that does not serve newer models
on the responses API and is not the host the official Copilot client
routes with (that comes from the token-exchange endpoint). --subscription
used the identical resolution, so it carried the same latent regression
as the non-subscription OAuth path.

Make Copilot host resolution override -> generic for BOTH --subscription
and the implicit OAuth path, and stop using user-info to route. Accounts
that require a dedicated host (enterprise / data residency) pin it via
GITHUB_COPILOT_API_URL. resolve_copilot_api_url no longer makes a network
call; _fetch_copilot_user_info is retained for token validation.

Update the subscription smoke tests that encoded the old account-host
assumption, and add wrap-level + unit coverage that --subscription routes
to the generic host even when user-info advertises an account host, and
that the GITHUB_COPILOT_API_URL override flows through both paths.

* docs(copilot): document generic-host routing + enterprise override (#610)

Spell out the routing contract introduced by the #610 fix so enterprise
users have a supported path. Headroom routes wrapped Copilot hosted
traffic (--subscription and OAuth) to the generic api.githubcopilot.com,
and accounts on a dedicated host (Enterprise Cloud data residency, egress
proxy) pin it via GITHUB_COPILOT_API_URL.

- copilot --help: note the generic host + GITHUB_COPILOT_API_URL override.
- TESTING-copilot-subscription.md: add "API host & Enterprise / data
  residency" section; correct the stale api.*.githubcopilot.com claim; and
  invite enterprise tenants who want token-exchange-based auto-detection to
  open an issue.
- integration-guide.md: short hosted-host + override note in the Copilot
  section.
2026-06-04 16:27:54 -07:00
Devanshi Vyas
44318f6e67
Merge pull request #611 from chopratejas/add-entmd
docs: Add and link ENTERPRISE.md
2026-06-04 16:05:12 -07:00
Devanshi Vyas
e6f788fb6f docs: add link to enterprisemd in README 2026-06-04 15:45:04 -07:00
Devanshi Vyas
3df22cad82 docs: add enterprise.md 2026-06-04 15:36:47 -07:00
Devanshi Vyas
a549f9e898
Merge pull request #609 from chopratejas/readme-fix
fix readme
2026-06-04 15:17:26 -07:00
Devanshi Vyas
63143608c5 fix readme 2026-06-04 15:14:36 -07:00
Tejas Chopra
fe50f9daed fix(ci): restore green lint gate on main
Merges of #564/#555 landed lint regressions that the old HF-429-masked CI hid:
- I001 unsorted imports in tests/repro_unsendable_panic.py (#564)
- ruff-format drift in helpers.py and code_compressor.py
- mypy arg-type on get_language() in code_compressor.py (#564 dropped the
  type: ignore the old get_parser() call had)

ruff check + ruff format --check + mypy headroom all green.
2026-06-04 14:15:49 -07:00
Tejas Chopra
5f5f261a03
Merge pull request #555 from ashishpatel26/fix/531-codex-wrap-fail-open-on-compression
fix(codex): auto-enable fail-open on compression timeout in headroom wrap codex (#531)
2026-06-04 13:54:02 -07:00
Tejas Chopra
e87cece9fd
Merge pull request #564 from mosatch/fix/thread-local-tree-sitter-parser
fix: use thread-local tree-sitter parsers to prevent unsendable panic
2026-06-04 13:52:58 -07:00
Tejas Chopra
06d7cb9e6c
Merge pull request #539 from neogenix/fix/security-and-reliability-blockers
fix(security): patch loopback guard, retry None raise, async subprocess, and cache race
2026-06-04 13:38:55 -07:00
Divyanshu Singh
db69ef8257 docs: add troubleshooting section and fix rust installation 2026-06-05 02:03:31 +05:30
Patrick Ancillotti
78f3a4dd3e fix(security): patch loopback guard, retry None raise, blocking subprocess, and cache stats race
- Add `Depends(_require_loopback)` to `/debug/memory` endpoint (was missing
  while /debug/tasks, /debug/ws-sessions, /debug/warmup all had it)
- Guard `raise last_error` when last_error is None (retry_max_attempts=0 path
  raised TypeError); add ProxyConfig.__post_init__ validation rejecting
  retry_max_attempts < 1 when retry_enabled=True
- Make initialize_context_tool_session_baseline async; offload subprocess via
  asyncio.to_thread so the blocking rtk/lean-ctx subprocess does not stall the
  event loop; update call sites in server.py
- Take snapshot list() of SemanticCache._cache.values() before iterating in
  get_memory_stats() to avoid dict-size-changed RuntimeError under async load
- Change memory_neo4j_password default from 'password' to '' and emit a
  logger.warning at startup when backend=qdrant-neo4j and password is empty
- Replace hardcoded NEO4J_AUTH=neo4j/password in docker-compose.yml with
  ${NEO4J_AUTH:-neo4j/devpassword}; add .env.example with CHANGEME placeholder
- Format tests/test_provider_proxy_routes.py (pre-existing ruff format drift)
2026-06-04 16:09:53 -04:00
Tejas Chopra
d8ac7472bf
Merge pull request #601 from chopratejas/ci/intelligent-parallel-pipeline
ci: cut over to intelligent + parallel pipeline (~3.7x faster)
2026-06-04 12:24:33 -07:00
Tejas Chopra
2ea548a86d ci: run relevance tests offline so fastembed doesn't 429 on cache HEAD 2026-06-04 12:16:04 -07:00
Tejas Chopra
51af24f7bf ci: cut over to the intelligent+parallel pipeline in ci.yml
Fold ci-fast.yml into ci.yml: change-detection (paths-filter), build the Rust
ext once (fast cargo profile) shared via artifact, lint once, prefetch the
embedding model once (authenticated) into a shared cache, and run the suite as
4 offline shards. Preserve commitlint, build smoke, workflow-validation, and
the docker/windows/macos e2e jobs (heavy ones gated on paths-filter). CPU-only
torch throughout; least-privilege permissions. Removes ci-fast.yml.

PRs run one Python version x 4 shards; multi-version on main is a follow-up.
2026-06-04 12:14:36 -07:00
Tejas Chopra
6f0dc32680 ci: pin least-privilege GITHUB_TOKEN permissions (contents: read)
Resolves the CodeQL 'workflow does not contain permissions' advisory. No job in
this workflow writes contents/PRs/releases, so read-only is sufficient.
2026-06-04 11:42:03 -07:00
Tejas Chopra
a08a0befa7 ci: build the CI test wheel with a fast cargo profile
The release profile (lto=thin, codegen-units=1) is great for the shipped wheel
but slow to compile — it was the build-wheel long pole (~3m38s) gating the test
shards. Add [profile.ci] (no LTO, codegen-units=256, opt-level=1) and build the
CI wheel with --profile ci. Does not affect --release / shipped wheels.
2026-06-04 11:38:30 -07:00
Tejas Chopra
9a16a59df4 ci: prefetch HF model once + run shards offline (kill the 429 herd)
Cold-cache run had all 4 shards download all-MiniLM in parallel -> HF 429'd a
shard. Add a prefetch-model job that fetches it once (huggingface_hub, no torch)
and warms the shared cache; shards then run with HF_HUB_OFFLINE=1 so they load
from cache with zero HF network calls (the 429 was on a cache-validation HEAD).
2026-06-04 11:10:02 -07:00
Tejas Chopra
184b29045b
Merge pull request #540 from neogenix/docs/fix-stale-and-incorrect-docs
docs: fix stale API references, retired class imports, and incorrect examples
2026-06-04 10:57:54 -07:00
Tejas Chopra
3599de8e7a
Merge branch 'main' into docs/fix-stale-and-incorrect-docs 2026-06-04 10:57:40 -07:00
Tejas Chopra
322d02efad ci: copy built _core.so into source tree so sharded tests import it
The prebuilt wheel installs headroom into site-packages, but tests run from the
repo root where ./headroom shadows it and has no compiled extension. Copy the
built _core.*.so into the source tree (no second cargo build) so
'import headroom._core' resolves.
2026-06-04 10:52:51 -07:00
Tejas Chopra
8062e7f7f5 ci: add experimental intelligent+parallel pipeline (ci-fast.yml)
Runs alongside ci.yml (does not touch required checks) so it can be validated
and timed on a real PR before cutover.

- changes: paths-filter skips everything for docs-only PRs
- build-wheel: compile the Rust ext ONCE via maturin, share via artifact
  (today ci.yml rebuilds it ~7x across the matrix/extras/agno/build jobs)
- lint: ruff + mypy once
- test: 4 parallel shards via pytest-split, each a fresh runner VM so the
  suite's shared-state tests (repo-root db, port 8787) can't collide
- CPU-only torch (drops the ~2.5GB CUDA stack) + cached HF model

Verified locally: YAML valid; pytest-split partitions the suite cleanly.
2026-06-04 10:35:54 -07:00
Tejas Chopra
6775e65049 ci(release-please): set versioned PR title pattern to fix tagging jam
Release PRs were titled 'chore: release main' (no version), so release-please
couldn't extract the version to tag on merge — leaving each merged release PR
'autorelease: pending' and aborting all subsequent releases (jammed #498, #594;
both required manual tag+publish recovery). Pin the title to
'chore: release ${version}' so the bot tags automatically on merge.
2026-06-04 09:56:35 -07:00
Devdeep Sarkar
074cd54b0a chore: trigger CI to resolve huggingface network timeout 2026-06-04 19:57:21 +05:30
Tejas Chopra
8006293e43
Merge pull request #594 from chopratejas/release-please--branches--main
chore: release main
2026-06-04 07:08:27 -07:00