Commit graph

114 commits

Author SHA1 Message Date
chopratejas
c1d2eec588 docs: improve discoverability for AI agents and search crawlers
Several signals AI agents and search engines use to discover and
install a project were misaligned or missing:

* ``docs/app/layout.tsx`` set ``metadataBase`` to
  ``https://chopratejas.github.io/headroom/`` while the live docs run
  on Vercel — every page's ``og:url`` and ``twitter:url`` resolved to
  a URL that returns 404 for ``/llms.txt``. Now points at the live
  Vercel host (overridable via ``NEXT_PUBLIC_SITE_URL`` for a future
  custom domain). Adds explicit ``openGraph`` and ``twitter`` metadata
  so social shares render a card with the project's pitch.
* No ``llms.txt`` at the GitHub repo root. AI agents crawling
  ``github.com/chopratejas/headroom/`` saw only the README. The new
  ``llms.txt`` follows the llmstxt.org convention: 1-line pitch,
  canonical docs links, copy-paste install commands (pip / npm /
  Docker / proxy / ``headroom wrap``), and entry points for the
  library, proxy, MCP server, and SDK integrations. Points at the
  Fumadocs-generated ``/llms.txt`` and ``/llms-full.txt`` for the
  full picture.
* ``pyproject.toml`` ``Documentation`` URL pointed at the GitHub
  README anchor. Updated to point at the docs site so PyPI visitors
  land on searchable docs, and adds an ``AI / LLM Index`` URL
  pointing at the Fumadocs ``/llms.txt``.
* No explicit AI-bot allow list. Added ``docs/app/robots.ts`` (Next
  13+ App Router convention) with explicit allows for GPTBot,
  ClaudeBot, PerplexityBot, Google-Extended, OAI-SearchBot,
  ChatGPT-User, Cohere-AI, CCBot, and Applebot-Extended. Wildcard
  allow as the catch-all. Advertises the sitemap.
* No ``sitemap.xml`` route. Added ``docs/app/sitemap.ts`` that pulls
  every Fumadocs page out of ``source`` (same source backing
  ``/llms.txt``, search, and OG images) so search and AI crawlers
  can enumerate doc pages without scraping HTML.
* README didn't tell AI agents where to look. Added a 2-line
  pointer near the top nav row: read ``/llms.txt`` here, or fetch
  the live index / full docs blob.

Also tightened the GitHub repo description and added five topics
(``claude-code``, ``cursor``, ``tokens``, ``prompt-engineering``,
``typescript``) via ``gh repo edit`` — that's already live on the
repo, not part of this commit.

No Python or Rust code changes; ``make ci-precheck`` was run to
confirm the test slice still passes.
2026-05-13 17:36:06 -07:00
chopratejas
0f6df1fef0 docs(readme): redesign with lean-ctx-style crispness
- ASCII block logo replaces plain # heading
- Power-stats line + nav links above the fold
- Time-boxed section headings (30s / 60s)
- What-it-does bullets pruned to one clause each
- Agent table notes trimmed to ≤5 words with ● markers
- Pipeline internals + provider slices moved to collapsed <details>
- New When-to-use / When-to-skip section
- GIFs centered via HTML with captions
- Integrations and What's-inside remain collapsed <details>
2026-05-11 22:33:03 -07:00
chopratejas
3432ee3a96 docs: add README redesign spec (lean-ctx parity + Headroom-first blend) 2026-05-11 22:22:34 -07:00
Gili Tzabari
4b061792b2 feat: add lean-ctx context tool support 2026-05-11 17:54:17 -04:00
Tejas Chopra
359e8c9a5b fix: align docs and prune dead compatibility surfaces 2026-05-09 14:07:59 -07:00
Daniel Munoz
9492386398 fix: harden release gating and clarify pipx compatibility 2026-05-06 12:41:17 +02:00
chopratejas
90ef66213d fix(proxy): PR-D3 Bedrock observability + auth-mode integration
Phase D close. Adds the operator-facing observability surface that
PRs D1 (native invoke) and D2 (streaming EventStream) deferred, and
wires the Phase F PR-F1 auth-mode classifier into the Bedrock route
so downstream cache/compression policy gates have something to read.

Changes
-------

* New `bedrock::auth_mode_layer` middleware. Classifies every
  inbound Bedrock request via F1's `classify`, coerces the result
  to `AuthMode::OAuth` per the Bedrock policy matrix (SigV4 IAM is
  OAuth-equivalent), and stores the resolved value in
  `request.extensions()` so PR-F2/F3 can read it without
  re-classifying. Mismatches are logged at WARN with
  `event=bedrock_auth_mode_unexpected` — no silent coercion.

* New `observability` module with three Prometheus families:
    - `bedrock_invoke_count_total{model, region, auth_mode}` (counter)
    - `bedrock_invoke_latency_seconds{model, region}` (histogram)
    - `bedrock_eventstream_message_count_total{model, region, event_type}`
      (counter)
  Registered lazily via `OnceLock` so per-request work is just
  `inc_with_label_values` / `observe`. Latency observed via an
  RAII `LatencyGuard` so every error path is instrumented; a
  future regression that adds a new return path can't drop the
  observation.

* New `GET /metrics` endpoint serves the registry in Prometheus
  text format. Mounted unconditionally — no feature flag gate — so
  scrape works regardless of which provider routes are mounted.

* Bedrock invoke + invoke-streaming handlers now extract
  `Extension<AuthMode>`, log it in their entry breadcrumbs
  (`event=bedrock_invoke_received`, `event=bedrock_invoke_streaming_received`),
  and pass `model`/`region` into `translate_stream` so per-message
  metrics carry the right labels.

* Operator docs at `docs/bedrock.md`: AWS credential chain,
  region/endpoint config, supported model IDs (`anthropic.*`
  literal-match — no regexes), compression behaviour, sample
  PromQL queries, structured-log correlation, rollback path.

Tests added (6, all green)
--------------------------

Auth-mode (`integration_bedrock_authmode.rs`):
  1. `bedrock_classified_as_oauth` — empty headers → OAuth in
     extensions.
  2. `oauth_policy_passthrough_prefer` — body byte-equal upstream;
     no auto cache_control / prompt_cache_key injected.

Metrics (`integration_bedrock_metrics.rs`):
  3. `metrics_increment_per_invoke` — 3 invokes → counter=3 with
     correct labels.
  4. `metrics_observe_latency` — 1 invoke → histogram count=1,
     sum>0.
  5. `eventstream_metrics_per_message_type` — 5 chunks → counter=5
     with `event_type=chunk`.
  6. `metrics_endpoint_serves_scrape` — `/metrics` returns 200,
     `text/plain`, all three metric families' HELP/TYPE lines
     present.

Each metrics test owns a unique (model, region) tuple so the
global `prometheus` registry — shared across parallel tests in
the same binary — gives each test isolated label rows. Without
isolation, parallel tests cross-contaminate counters.

Constraints honoured
--------------------

* No silent fallbacks — auth-mode coercion is logged at WARN.
* No hardcodes — region from `--bedrock-region`, model from axum
  path parameter.
* No regexes — vendor prefix is literal `anthropic.`.
* Comprehensive structured logs — every metric increment paired
  with `tracing::debug!` carrying the same labels for incident
  correlation.
* Performant — `OnceLock`-cached descriptors, RAII guard, total
  D3 overhead well under 1us per request.
* Cardinality bounded — labels driven by config + bounded enums,
  never by user-controlled bytes.

Live cloud validation deferred
------------------------------

The wiremock-backed integration tests are the canonical correctness
gate for D3. A real Bedrock smoke test requires `bedrock:InvokeModel`
permissions in the developer's AWS account and is documented in
`docs/bedrock.md` — both D1 and D2 hit sandbox permission issues
trying this path; D3 follows the same convention.

Stacked on
----------

PR #364 (D1 native invoke), PR #365 (D2 streaming EventStream),
PR #366 (F1 classifier helper). Merge those first; this PR will be
rebased onto main once they land.
2026-05-04 11:07:47 -07:00
chopratejas
ca9de93cfc fix: PR-F1 classify_auth_mode helper (Phase F kickoff)
Add the classify_auth_mode helper that maps inbound request headers to
one of three auth modes — Payg / OAuth / Subscription — at request
entry. The mode is the first-class policy axis Phase F's remaining PRs
(F2 cache+lossy gates, F3 TOIN per-tenant aggregation, F4
X-Forwarded-* skip) gate behavior on.

Detection rules (most-specific signal wins):
- Subscription UA prefix in user-agent → Subscription
- Bearer sk-ant-oat-* → OAuth (Claude Pro/Max)
- Bearer sk-ant-api* / Bearer sk-* → Payg
- Bearer <jwt> (3 dot-segments) → OAuth (Codex/Cursor/Copilot)
- Authorization present but not Bearer (AWS SigV4) → OAuth (Bedrock)
- x-api-key / x-goog-api-key → Payg
- Default → Payg

Hard constraints met: pure function, no regex, no silent fallback
(non-UTF-8 headers warn! and fall through), no hardcoded list (UA
prefixes in module-scope const ready to swap for config in a follow-up).

Files:
- crates/headroom-core/src/auth_mode.rs (new) — Rust impl
- crates/headroom-core/tests/auth_mode.rs (new) — 14 unit + 1 perf
- crates/headroom-core/benches/auth_mode.rs (new) — Criterion bench
- crates/headroom-core/Cargo.toml — add http dep + bench entry
- crates/headroom-core/src/lib.rs — pub mod auth_mode
- crates/headroom-proxy/src/proxy.rs — classify at request entry,
  store in extensions, log event=auth_mode_classified
- headroom/proxy/auth_mode.py (new) — Python port (parity)
- headroom/proxy/handlers/anthropic.py — wire into messages handler
- headroom/proxy/handlers/openai.py — wire into chat + responses
- tests/test_auth_mode.py (new) — 23 Python parity tests
- docs/auth-modes.md (new) — detection rules + how-to-extend

Tests: 15 Rust + 23 Python all green. cargo fmt + clippy + workspace
tests + ci-precheck all green.

Performance (criterion, M-series):
- auth_mode/classify/empty: 68 ns
- auth_mode/classify/payg_anthropic_api_key: 75 ns
- auth_mode/classify/oauth_jwt: 182 ns
- auth_mode/classify/subscription_claude_code: 81 ns

All paths well under the <10us budget (~50-150x headroom).

Refs: REALIGNMENT/08-phase-F-auth-mode.md PR-F1.
2026-05-03 17:20:14 -07:00
chopratejas
2e874c5e3e fix: A5 — strip x-headroom-* from upstream-bound headers (P5-49)
Eliminate P5-49: every Python forwarder and the Rust transparent proxy
now drop internal `x-headroom-*` request headers (`x-headroom-bypass`,
`x-headroom-mode`, `x-headroom-user-id`, `x-headroom-stack`,
`x-headroom-base-url`) before the upstream call. Stops fingerprinting
of the proxy by subscription-revocation enforcers and prevents leakage
of internal user-id / stack / base-url internals to whichever vendor
terminates the request.

Python:
- `_strip_internal_headers(headers)` in `headroom/proxy/helpers.py`
  returns a NEW dict with `x-headroom-*` keys removed (case-insensitive
  prefix match, no regex). Pure function. Operator opt-in
  `HEADROOM_STRIP_INTERNAL_HEADERS=disabled` keeps internal headers in
  the upstream-bound dict for diagnostic shadow tracing — explicit, not
  a fallback.
- Strip applied at every handler entry capture in `anthropic.py`,
  `openai.py`, `batch.py`, `gemini.py` (chat completions, responses,
  WebSocket handshake, Copilot passthrough, batch passthroughs, Gemini
  generate / stream / countTokens / cloudcode-assist, Anthropic
  passthrough + batch results). Inbound reads of x-headroom (bypass
  gating, memory user-id) migrated to `request.headers.get(...)` so
  they continue working off the original dict.
- `log_outbound_headers` emits `event=outbound_headers forwarder=...
  stripped_count=N request_id=...` per call. Never logs header values.

Rust (crates/headroom-proxy):
- `strip_internal_headers(&mut HeaderMap)` and `is_internal_header`
  helpers in `src/headers.rs`. `build_forward_request_headers` accepts
  a `strip_internal: bool` so the same path serves HTTP and WebSocket.
- `Config::strip_internal_headers: StripInternalHeaders` driven by CLI
  flag `--strip-internal-headers` and env var
  `HEADROOM_PROXY_STRIP_INTERNAL_HEADERS` (default `enabled`).
- `proxy.rs` and `websocket.rs` call `build_forward_request_headers`
  with the resolved policy; structured `tracing::info!` /
  `tracing::warn!` line per request describes the strip decision.

Tests: 24 Python (`tests/test_header_isolation.py`) + 4 Rust
integration (`crates/headroom-proxy/tests/integration_headers.rs`) +
4 Rust unit tests in `headers.rs`. Covers every named header
(`bypass`, `mode`, `user-id`, `stack`, `base-url`), case-insensitive
prefix matching, legitimate-headers passthrough, the `disabled`
operator-opt-in mode, and that the inbound bypass-gating read path
is unaffected by the strip.

Acceptance: targeted `pytest -x` suite green (87 tests across
test_header_isolation, test_proxy_byte_faithful_forwarding,
test_proxy_anthropic_cache_stability, test_proxy_system_prompt_immutable,
test_proxy_openai_cache_stability, test_proxy_pipeline_lifecycle).
`cargo test -p headroom-proxy` green (23 tests across all integrations
plus 7 lib unit tests). `cargo clippy -p headroom-proxy -- -D warnings`
clean. `cargo fmt --all -- --check` clean. `cargo test --workspace`
green (~900 tests total).

Per realignment build constraints: configurable (env + CLI), no
hardcodes, no regex (pure `.lower().starts_with()` match), no silent
fallbacks (`disabled` is loud operator opt-in), structured logs
(`event=outbound_headers`).

Remaining `x-headroom-` references in `headroom/proxy/handlers/` are
inbound-read sites only: `request.headers.get("x-headroom-bypass")` /
`x-headroom-mode` for behavior gating, `request.headers.get
("x-headroom-user-id")` for memory user-id resolution, and `ws_headers
.get(...)` on the WebSocket inbound path. Response-side `X-Headroom-*`
injection (e.g. `x-headroom-tokens-saved`) is unrelated to upstream
forwarding and untouched.
2026-05-02 09:35:27 -07:00
chopratejas
f0dcc02775 fix: A3 — byte-faithful Python forwarders; serialize canonical only when mutated
Eliminates P0-2 universally. Every Python forwarder (server.py
`_retry_request`, handlers/streaming.py `_stream_response`,
handlers/openai.py `_ws_http_fallback`, handlers/batch.py `_batch_passthrough`
+ batch-create + Google batch passthrough, handlers/anthropic.py CCR
continuation + batch endpoint) now switches from `httpx ... json=body` to
`httpx ... content=raw_bytes`. The default httpx JSON encoder was
re-serializing every request with `, `/`: ` separators and `\\uXXXX` ASCII
escapes — collapsing Anthropic prompt-cache hit-rate.

Forwarder strategy:
  - unmutated body → forward `await request.body()` verbatim;
  - mutated body  → re-serialize once via the new
    `serialize_body_canonical(body) -> bytes` helper (compact separators,
    `ensure_ascii=False`, dict insertion order preserved).

`HEADROOM_PROXY_PYTHON_FORWARDER_MODE` env var configures the mode:
  - `byte_faithful` (default) — the new behavior;
  - `legacy_json_kwarg` — explicit operator opt-in for emergency rollback.
Documented in `docs/content/docs/configuration.mdx`. NOT a fallback —
unknown values raise loudly per build constraint #4.

`BodyMutationTracker` accompanies each request through the handler so
transform sites mark the tracker (`memory_injection`,
`image_compression`, `compression_*`, `batch_compression`,
`ccr_continuation`, etc.). At forwarder dispatch we additionally compare
the final body dict against the parsed original bytes as a structural
safety net — any silent mutation we missed still triggers canonical
re-serialization.

A2 follow-up: `handlers/openai.py:534-540` (Chat Completions memory
injection) was prepending a system message; replaced with
`append_text_to_latest_user_chat_message`, the OpenAI Chat Completions
analog of `_append_context_to_latest_non_frozen_user_turn`. The cache
hot zone (system messages) is now sacrosanct on /v1/chat/completions
too. Honors `HEADROOM_MEMORY_INJECTION_MODE=disabled`.

Structured logging: every forwarder emits an `event=outbound_request`
log line with `forwarder`, `path`, `body_bytes`, `body_mutated`,
`mutation_reasons`, `source` (passthrough|canonical|legacy),
`request_id`. Never logs Authorization or full body.

`_read_request_json` factored to share `_read_request_body_bytes` with
new `read_request_json_with_bytes` so the anthropic handler can capture
both the parsed dict and the original (decompressed) bytes.

Tests:
  - `tests/test_proxy_byte_faithful_forwarding.py` (28 tests):
    SHA-256 byte-equality on /v1/messages and streaming, unicode
    preservation, numeric precision, mutation-tracker invariants,
    canonical-serializer properties, legacy-mode rollback, OpenAI
    Chat memory routing.
  - Existing test mocks updated to accept the new `**kwargs` on
    `_retry_request` (no behavior change).
  - `tests/test_proxy_handlers_batch.py` updated to read the captured
    `content=` bytes (formerly `json=`).
  - One A2 test corrected (`test_anthropic_tool_sort_and_context_append_helpers`)
    to match the live-zone-tail semantics introduced by A2.

Constraints satisfied: configurable env var; no new regex / hardcodes;
no silent fallback (`legacy_json_kwarg` is operator opt-in);
performant (`prepare_outbound_body_bytes` is O(1) for passthrough);
elegant single-responsibility helpers; structured tracing logs.
2026-05-02 09:02:10 -07:00
chopratejas
4429a11166 Merge remote-tracking branch 'origin/main' into rust-rewrite
# Conflicts:
#	headroom/proxy/server.py
2026-04-25 13:01:37 -07:00
dependabot[bot]
2f659535d2
chore(deps): bump the npm_and_yarn group across 3 directories with 4 updates
Bumps the npm_and_yarn group with 1 update in the /sdk/typescript directory: [esbuild](https://github.com/evanw/esbuild).
Bumps the npm_and_yarn group with 1 update in the /plugins/openclaw directory: [esbuild](https://github.com/evanw/esbuild).
Bumps the npm_and_yarn group with 2 updates in the /docs directory: [postcss](https://github.com/postcss/postcss) and [next](https://github.com/vercel/next.js).


Updates `esbuild` from 0.21.5 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.21.5...v0.27.4)

Updates `postcss` from 8.5.8 to 8.5.10
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.10)

Updates `vite` from 5.4.21 to 8.0.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.10/packages/vite)

Updates `esbuild` from 0.21.5 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.21.5...v0.27.4)

Updates `postcss` from 8.5.8 to 8.5.10
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.10)

Updates `vite` from 5.4.21 to 8.0.10
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.10/packages/vite)

Updates `postcss` from 8.5.8 to 8.5.10
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.8...8.5.10)

Updates `next` from 16.2.2 to 16.2.4
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v16.2.2...v16.2.4)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.27.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.10
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 8.0.10
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: esbuild
  dependency-version: 0.27.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.10
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: vite
  dependency-version: 8.0.10
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: postcss
  dependency-version: 8.5.10
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: next
  dependency-version: 16.2.4
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-24 22:15:53 +00:00
chopratejas
0414cb70e4 feat(rust): scaffold workspace + parity harness (phase-0)
Bootstrap the Rust port of Headroom. Additive only — no existing Python
code modified. Ships the workshop, not the widgets.

Layout
  Cargo.toml (workspace) + rust-toolchain.toml
  crates/headroom-core    — transform library, stub only
  crates/headroom-proxy   — axum binary, /healthz only
  crates/headroom-py      — PyO3 cdylib, exposes headroom._core.hello()
  crates/headroom-parity  — Rust-vs-Python oracle harness + parity-run CLI

Tooling
  Makefile: test, test-parity, bench, build-proxy, build-wheel, fmt, lint
  .github/workflows/rust.yml: test, wheels (linux/mac), audit, parity-nightly
  deny.toml for cargo-deny

Parity corpus
  tests/parity/recorder.py + scripts/record_fixtures.py
  125 recorded fixtures across 5 leaf transforms (ccr, tokenizer,
  log_compressor, diff_compressor, cache_aligner)

Docs
  RUST_DEV.md — developer setup and workspace reference
  docs/spec/022-rust-migration.md — migration plan and stage breakdown

.gitignore: whitelist scripts/record_fixtures.py; ignore target/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:39:48 -07:00
JD Davis
e854ba1771
Merge branch 'main' into fix/python-github-packages-publish 2026-04-21 07:10:17 -05:00
Tejas Chopra
724c2987b3
Merge pull request #221 from chopratejas/docs/port-wiki-pages-to-fumadocs
docs: port Docker-native, filesystem-contract, and persistent-installs pages to Fumadocs
2026-04-20 23:07:40 -07:00
chopratejas
6d250554f7 docs: port Docker-native, filesystem-contract, and persistent-installs pages to Fumadocs
These three wiki pages documented shipped features (PRs #139, #145, #191)
but were never ported into the new Fumadocs site introduced in 911eb85
("new docs UI + ts doc coverage"). Users browsing docs.* couldn't find
the Docker-native install flow, the canonical filesystem contract, or
the persistent-install CLI surface.

- Add docs/content/docs/docker-install.mdx (one-line installer, native
  wrapper behavior, persistent-docker lifecycle, Compose runtime).
- Add docs/content/docs/filesystem-contract.mdx (two-root model,
  precedence, bucket assignments, Docker overlap between
  HEADROOM_WORKSPACE and HEADROOM_WORKSPACE_DIR).
- Add docs/content/docs/persistent-installs.mdx (runtime matrix,
  presets, scopes, health/wrap behavior, Docker-native relationship).
- installation.mdx: add a Callout in the Docker section linking to the
  new docker-install page so pip/npm/docker landing users find it.
- meta.json: surface the three new pages in the sidebar under
  Getting Started and Configuration.

Wiki source files in wiki/ are left in place for now; they can be
deprecated in a follow-up once the new site is confirmed as canonical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 23:05:15 -07:00
JerrettDavis
f5dfdda253 ci: publish Python distributions to GitHub releases 2026-04-20 23:32:31 -05:00
JerrettDavis
d239e6f41f fix: complete fork-friendly release publishing
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 19:00:43 -05:00
Kayzo
6690740218 chore(merge): resolve upstream main conflicts for pi codex branch 2026-04-20 21:50:33 +00:00
JerrettDavis
3ddc7ff33a fix: restore release and compress regressions
Fix workflow validation failures by wiring detect-version outputs into all
release publish jobs, renaming the GitHub Packages skip variable to a
valid Actions variable name, and adjusting the macOS PATH export for
actionlint.

Also make min_tokens_to_compress use token counting instead of whitespace
splits so compact JSON tool outputs still compress after merging the
latest main branch changes, and add a regression test for that path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 16:01:57 -05:00
JerrettDavis
d8c2ae88cd ci: validate release workflows with act
Add a workflow-validation CI job that installs actionlint and act,
checks the release and Docker workflows against checked-in event
fixtures, and shares the same validation script developers can run
locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:38:10 -05:00
JerrettDavis
a36f3e2d3e fix: publish release artifacts and docker together
Call the Docker workflow from the release pipeline so Docker publishes in

the same run, build npm tarballs alongside Python distributions, and

attach those artifacts to the GitHub release page.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 21:33:37 -05:00
Tejas Chopra
80cfcd7e5b
Merge pull request #192 from JerrettDavis/feature/spec-183
feat(specs): #183 scaffold initial application specs.
2026-04-17 11:00:33 -07:00
Kayzo
4bdc1482af feat: add Pi/Codex and Cloud Code Assist compatibility routes
- Adds /v1/codex/responses aliases for OpenAI Codex clients configured with /v1 base URLs
- Uses JWT-derived account routing for /v1/responses/* subpaths (compact, cancel, etc)
- Adds /v1internal:streamGenerateContent aliases for Cloud Code Assist / Antigravity
- Preserves upstream HTTP error status/body in StreamingResponse (fixes empty SSE drops)
2026-04-17 17:45:08 +00:00
JerrettDavis
bde7aa9c30 fix: align docker image versions with releases
Derive the exact Docker image version from the release tag or manual
workflow input, sync versioned files in the build workspace before the
image build, and publish an explicit matching image tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-17 12:17:49 -05:00
JerrettDavis
8872b8be3b fix: take highest release bump across unreleased commits
Determine the release bump from all unreleased commits since the previous
release tag and apply the highest required semantic version increment.
This keeps feat commits at a minor bump unless a breaking change requires
major, even when later patch-level commits are present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 23:38:29 -05:00
JerrettDavis
ec305efcd9 docs: move spec from specify/ to docs/spec/
Align with SpecKit's canonical docs/ structure. Update .gitignore
comment to reflect new location.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 21:15:04 -05:00
JerrettDavis
1192f657eb docs: document HEADROOM_CONFIG_DIR / HEADROOM_WORKSPACE_DIR filesystem contract
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:24:17 -05:00
JerrettDavis
0ba104248c fix: repair release and docs pipelines
Grant the release job contents write permission so GitHub releases can be created, and add the missing docs/overrides directory required by MkDocs deployment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 12:53:51 -05:00
JerrettDavis
049b3527a8 refactor: implement canonical+commit-height release algorithm
The release workflow now uses a loop-free algorithm:
- pyproject.toml is the canonical source of truth (never committed by workflow)
- Git tags use v{canonical}.{height} format (e.g. v0.5.25.3)
- npm publishes use 3-part semver bumped from canonical
- No commit step eliminates infinite release loops
- paths-ignore reduces unnecessary workflow triggers

Also:
- Add .releaseetadata to .gitignore
- Separate npm_version output for semver-compatible npm publishing
- create-release no longer blocks on publish jobs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 21:26:13 -05:00
JerrettDavis
2df4a53be4 docs: add releases & CI/CD documentation 2026-04-15 20:33:06 -05:00
Tejas Chopra
54ae7b9928
Merge pull request #147 from JerrettDavis/feat/anthropic-usage-insights
feat: AI quota & rate-limit tracking — Anthropic, OpenAI Codex, and GitHub Copilot
2026-04-12 10:54:22 -07:00
Adib Mohsin
ef23064358 tokens saved grid 2026-04-12 13:42:20 +06:00
Adib Mohsin
911eb85a44 new docs UI + ts doc coverage 2026-04-12 13:15:58 +06:00
JerrettDavis
788d0e7265 feat(dashboard): expand Codex and Copilot cards to match Anthropic card layout
- Codex: 2-column Primary/Secondary window grid (h-3 bars, window labels,
  reset countdowns); Credits moved to bordered section like Anthropic
  extra-usage panel
- Copilot: 3-column category grid (Chat/Completions/Premium) with big
  used/entitlement numbers, color-coded % bars, overage-permitted status
  per category; Monthly Reset section with month-elapsed progress bar and
  formatMonthlyReset() helper
- All three provider cards now share consistent visual language:
  uppercase section labels, h-3 progress bars, border-t dividers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-12 00:20:10 -05:00
JerrettDavis
d11feea511 feat: add GitHub Copilot monthly quota tracking
Adds passive tracking of GitHub Copilot per-category monthly quotas
(chat, completions, premium_interactions) via GET /copilot_internal/user
on api.github.com.

Token discovery checks environment variables in priority order:
  GITHUB_COPILOT_GITHUB_TOKEN > GITHUB_TOKEN >
  COPILOT_GITHUB_TOKEN > GITHUB_COPILOT_API_TOKEN

- headroom/subscription/copilot_quota.py: CopilotQuotaCategory,
  CopilotQuotaSnapshot, CopilotQuotaState, parse_copilot_quota(),
  discover_github_token(), _CopilotQuotaTracker singleton (60s poll)
- headroom/subscription/__init__.py: export new symbols
- headroom/proxy/server.py: start/stop tracker in lifecycle;
  _get_copilot_quota_stats(); copilot_quota key in /stats
- dashboard.html: GitHub Copilot Quota panel with per-category
  progress bars, remaining counts, overage alerts, reset date
- tests/test_copilot_quota.py: 25 unit tests (all pass)
- docs/screenshots/subscription_window_active.png: updated to show
  all three panels (Anthropic + Codex + GitHub Copilot)

Env vars sourced from @github/copilot v1.0.24 app.js (k6e array).
API schema sourced from copilot_internal/user via eBo/QRt zod schemas.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 23:58:44 -05:00
JerrettDavis
a3abbec4a3 feat: add OpenAI Codex rate-limit window tracking
Passively capture x-codex-* response headers from proxied Codex API
calls and surface them in /stats and the dashboard.

Unlike Anthropic subscription window tracking (which polls a dedicated
OAuth endpoint), Codex embeds rate-limit data directly in every API
response header — no polling, no new credentials needed.

Changes:
- headroom/subscription/codex_rate_limits.py: new module with
  CodexRateLimitWindow, CodexCreditsSnapshot, CodexRateLimitSnapshot
  data models and a thread-safe CodexRateLimitState singleton;
  parse_codex_rate_limits() parses x-codex-primary/secondary-used-percent,
  window-minutes, reset-at, credits, limit-name, and promo-message headers
- headroom/subscription/__init__.py: re-export new public symbols
- headroom/proxy/handlers/openai.py: call
  get_codex_rate_limit_state().update_from_headers() after each
  proxied /v1/chat/completions and /v1/responses response
- headroom/proxy/server.py: add codex_rate_limits key to /stats
  via _get_codex_rate_limit_stats() helper
- headroom/dashboard/templates/dashboard.html: new OpenAI Codex
  Rate-Limit Window panel (primary + secondary progress bars, credits
  balance, limit name, reset countdown); hidden when no data
- tests/test_codex_rate_limits.py: 25 unit tests covering parsing,
  window labels, reset time, credits, state updates
- docs/screenshots/subscription_window_active.png: updated screenshot
  showing both Anthropic and Codex panels in the real dashboard

Header schema (from codex-rs/codex-api/src/rate_limits.rs):
  x-codex-primary-used-percent / x-codex-primary-window-minutes /
  x-codex-primary-reset-at  (and secondary- variants)
  x-codex-credits-has-credits / -unlimited / -balance
  x-codex-limit-name / x-codex-promo-message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 23:34:19 -05:00
JerrettDavis
86efb59615 docs: replace mock screenshots with real dashboard renders
Active state shows the full headroom dashboard with the Anthropic
Subscription Window panel integrated in-line after Savings Breakdown,
including 5h/7d utilisation bars, overage credit bar, Headroom
contribution grid, and anomaly detection alert.

Inactive state shows the standard dashboard without the panel —
it is conditionally rendered only when subscription_window data
is present in /stats.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 22:30:43 -05:00
JerrettDavis
958c731e75 docs: add dashboard screenshots for subscription window panel
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 21:58:28 -05:00
JerrettDavis
865bef2216 fix: harden persistent install wrappers and review gaps
Align Docker-native wrapper help and runtime behavior with the Python install contract, including persistent deployment metadata, baked install-image defaults, and explicit unsupported wrap targets.

Harden the Python persistent-install path with profile validation, safer provider-scope handling, Windows environment restoration, runtime parity improvements, and rollback-safe apply/update behavior.

Update README, Docker install docs, CI, and focused regressions to cover the Windows BOM failure, wrapper parity, compose coverage, and Docker-native wrap behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 18:03:21 -05:00
JerrettDavis
b325a06aae feat: harden persistent install wrappers
Tighten Docker-native bash and PowerShell wrapper validation for wrap and proxy flows, pin the bash wrapper to the install-time interpreter, clean up failed persistent container starts, and extend docs, CI, e2e, and native installer coverage for persistent Docker installs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 15:56:18 -05:00
JerrettDavis
21896a095c feat: add persistent install lifecycle management
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 13:47:05 -05:00
Tejas Chopra
a245234f5a
Merge pull request #130 from Gyeonghun-Park/feat/cli-llm-backend
feat(learn): add CLI-based LLM backends for keyless headroom learn
2026-04-11 09:05:26 -07:00
Tejas Chopra
9f124c99ff
Merge pull request #139 from JerrettDavis/feat/docker-native-cli
feat(cli): add Docker-native install flow and parity docs
2026-04-11 09:05:00 -07:00
JerrettDavis
9fa1763087 feat: add copilot CLI wrap support
Add headroom wrap copilot with backend-aware provider routing, health metadata for running proxy detection, focused Copilot tests, and docs updates across the main integration surfaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 01:20:17 -05:00
JerrettDavis
777faa85a7 docs: fix CLI parity matrix table
Separate the Docker-native parity legend from the table header so Markdown renders the matrix correctly in cli.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 00:11:10 -05:00
JerrettDavis
a1dcda6bc4 feat(cli): support OpenClaw in Docker-native installs
Add host-managed OpenClaw wrap and unwrap flows to the Docker-native wrappers so the installed headroom script can configure the OpenClaw plugin on the host while keeping Headroom itself in Docker. Reuse hidden prepare-only hooks for OpenClaw config payloads, preserve existing plugin metadata on unwrap, and update the Docker-native and integration docs to reflect the supported flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 00:04:15 -05:00
JerrettDavis
38b1483a76 feat(cli): add Docker-native install flow and parity docs
Add system-native install scripts and host wrappers for running Headroom from Docker while keeping wrapped tools on the host. Document the Docker-native path, add a complete CLI reference with help output and parity details, and add support for root help/version aliases and proxy env-based binding behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 23:27:24 -05:00
Gyeonghun Park
da4b971128 Merge remote-tracking branch 'origin/main' into feat/cli-llm-backend
# Conflicts:
#	docs/learn.md
2026-04-11 10:05:45 +09:00
Tejas Chopra
5b81395511
Merge pull request #132 from JerrettDavis/jd/devcontainers
feat: add reproducible devcontainers
2026-04-10 14:54:27 -07:00