headroom/plugins
Rod Boev c19347c310
fix(opencode): preserve custom OpenAI gateway paths (#1596)
## Description

Custom OpenAI-compatible gateways mounted under provider-specific
prefixes could miss Headroom's dedicated OpenAI compression routes when
used through the OpenCode transport. A request such as
`https://open.bigmodel.cn/api/coding/paas/v4/chat/completions` was
replayed to the proxy at `/api/coding/paas/v4/chat/completions`, so the
proxy selected catch-all passthrough instead of `/v1/chat/completions`.

This change keeps the proxy-facing entrypoints stable on
`/v1/chat/completions` and `/v1/responses` for OpenAI-compatible
suffixes, while preserving the original upstream path in an internal
header so the dedicated OpenAI handlers can reconstruct the real
provider URL.

Closes #1582

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Normalize opencode-routed OpenAI-compatible `/chat/completions` and
`/responses` requests onto the proxy's stable `/v1/*` routes.
- Preserve the original upstream pathname in an internal
`x-headroom-original-path` signal for dedicated OpenAI handler
reconstruction.
- Reconstruct dedicated OpenAI upstream URLs from `x-headroom-base-url`
plus the preserved path prefix, while preserving request query strings
and rejecting non-HTTP base hints.
- Keep nearby non-OpenAI paths such as `/base/v1/messages` on existing
passthrough behavior.
- Add focused transport and proxy regression coverage for prefixed
gateway paths, invalid fallback cases, and internal-header stripping.

## Testing

- [x] Transport regression tests pass (`npm --prefix plugins/opencode
test -- src/transport.test.ts`)
- [x] Proxy regression tests pass (`uv run pytest
tests/test_proxy/test_openai_transport_path_prefix.py -q`)
- [x] Linting passes (`uv run ruff check
headroom/proxy/handlers/openai.py
tests/test_proxy/test_openai_transport_path_prefix.py`)
- [x] Type checking passes (`npm --prefix plugins/opencode run
typecheck`)
- [x] New tests added for the bugfix
- [ ] Manual testing performed

### Test Output

```text
npm --prefix plugins/opencode test -- src/transport.test.ts
PASS, 11 tests passed.

npm --prefix plugins/opencode run typecheck
PASS

uv run pytest tests/test_proxy/test_openai_transport_path_prefix.py -q
PASS, 7 tests passed.

uv run ruff check headroom/proxy/handlers/openai.py tests/test_proxy/test_openai_transport_path_prefix.py
PASS, all checks passed.
```

## Real Behavior Proof

- Environment: Windows, Python 3.12 via `uv`, Node 18+, focused OpenCode
transport and proxy handler tests.
- Exact command / steps: on `origin/main`, copy the updated
`plugins/opencode/src/transport.test.ts` into a base worktree and run
`npm --prefix plugins/opencode test -- src/transport.test.ts`; on this
branch, rerun that transport test plus `npm --prefix plugins/opencode
run typecheck` and `uv run pytest
tests/test_proxy/test_openai_transport_path_prefix.py -q`.
- Observed result: the base worktree fails because prefixed
`/chat/completions` and `/responses` requests still enter the proxy at
their provider path, while this branch passes with
`/v1/chat/completions` and `/v1/responses`, preserves
`x-headroom-original-path`, reconstructs the provider-prefixed upstream
URL and query string, falls back safely on invalid hints, and keeps
nearby `/base/v1/messages` traffic on passthrough.
- Not tested: full CI suite, live BigModel traffic, and generic
catch-all passthrough compression.

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

## Additional Notes

This completes the transport contract introduced in
https://github.com/headroomlabs-ai/headroom/pull/1573 by keeping
prefixed OpenAI-compatible traffic on Headroom's stable `/v1/*` surface
while preserving the real upstream path for dedicated-handler
reconstruction.

https://github.com/headroomlabs-ai/headroom/pull/1367 is adjacent global
proxy configuration work for direct deployments; this PR is the
per-request OpenCode transport fix for custom upstream path prefixes.

`CHANGELOG.md` is intentionally unchanged because this repo's release
pipeline generates changelog entries from conventional commits.

This stays scoped to `/chat/completions` and `/responses` suffixes.
Generic catch-all passthrough compression remains separate from this
bugfix slice.
2026-06-30 08:41:42 -07:00
..
headroom-agent-hooks ci(release): align manifest + pyproject + package.json to 0.22.3 2026-05-25 18:41:38 -07:00
headroom-oauth2 chore: remove committed node_modules + stray/internal markdown (repo hygiene) (#1528) 2026-06-27 23:32:54 -07:00
hermes fix(ccr): make headroom_retrieve a hash-only full-content lookup (#1532) 2026-06-28 10:32:43 -07:00
openclaw chore: release main (#1441) 2026-06-29 12:53:17 -07:00
opencode fix(opencode): preserve custom OpenAI gateway paths (#1596) 2026-06-30 08:41:42 -07:00