Skip to content

fix(wrap): let wrap opencode target a third-party OpenAI-compatible upstream - #3125

Open
Ayushraj06-bit wants to merge 1 commit into
headroomlabs-ai:mainfrom
Ayushraj06-bit:fix/wrap-opencode-openai-api-url-3107
Open

fix(wrap): let wrap opencode target a third-party OpenAI-compatible upstream#3125
Ayushraj06-bit wants to merge 1 commit into
headroomlabs-ai:mainfrom
Ayushraj06-bit:fix/wrap-opencode-openai-api-url-3107

Conversation

@Ayushraj06-bit

@Ayushraj06-bit Ayushraj06-bit commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Description

headroom wrap opencode had no way to say where OpenAI-compatible traffic should go. opencode() forwarded openai_api_url to _ensure_proxy only for --copilot-subscription; every other run inherited the proxy default, https://api.openai.com/v1. A user following the README's agent-wrap table with a DeepSeek key therefore had that key presented to OpenAI, which rejected it with 401 Incorrect API key provided and pointed them at a platform.openai.com dashboard for a key they never had. Meanwhile docs/content/docs/opencode-deepseek.mdx said "Do not use headroom wrap", with nothing reconciling the two.

This adds the missing plumbing (--openai-api-url / OPENAI_TARGET_API_URL, mirroring headroom proxy) and the documentation the issue asked for.

Closes #3107

Type of Change

  • 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

  • headroom/cli/wrap.py: wrap opencode gains --openai-api-url, with envvar="OPENAI_TARGET_API_URL", mirroring headroom proxy --openai-api-url. It is passed to _ensure_proxy, which already knows how to start the proxy with it and how to report a mismatch against an already-running proxy.
  • headroom/cli/wrap.py: combining --openai-api-url with --copilot-subscription raises a ClickException before anything is mutated — the subscription resolves its own upstream, so a manual override would silently fight it.
  • headroom/cli/wrap.py: the opencode docstring documents the default upstream and both invocation forms.
  • docs/content/docs/opencode-deepseek.mdx: new "Common issues" entry for the Incorrect API key provided 401 (it comes from OpenAI, not DeepSeek) and a "When is headroom wrap opencode safe?" section — safe as-is for the default OpenAI/Anthropic upstreams, needs --openai-api-url for any third-party OpenAI-compatible provider, manual config when you want to pin model IDs and limits. The "Models appear but requests fail" entry and the "What's NOT in this guide" bullet are reworded to match, replacing the blanket "do not use headroom wrap".
  • README.md: footnote on the OpenCode row of the agent compatibility matrix naming the default upstream and the flag/env var.
  • tests/test_cli/test_wrap_opencode.py: four tests covering the flag, the env var, the unset default, and the --copilot-subscription rejection.

Testing

  • Unit tests pass (pytest)
  • Linting passes (ruff check .)
  • Type checking passes (mypy headroom)
  • New tests added for new functionality
  • Manual testing performed

Three failures in the wider tests/test_cli run are pre-existing and unrelated — they reproduce identically on a clean main (git stash) and are Windows-specific symlink/settings-path issues in test_recover_codex.py and test_unwrap_claude.py.

Test Output

$ python -m pytest tests/test_cli/test_wrap_opencode.py -q
44 passed, 1 warning in 1.45s

$ python -m pytest tests/test_cli -q
3 failed, 681 passed, 2 skipped, 1 warning in 59.22s
FAILED tests/test_cli/test_recover_codex.py::test_discover_dangling_homes_only_returns_codex_homes
FAILED tests/test_cli/test_recover_codex.py::test_recovery_never_writes_through_target_symlinks
FAILED tests/test_cli/test_unwrap_claude.py::test_unwrap_claude_removes_mcp_purges_retired_hook_and_stops_proxy

$ git stash && python -m pytest tests/test_cli/test_recover_codex.py tests/test_cli/test_unwrap_claude.py -q
3 failed, 41 passed, 1 skipped, 1 warning in 2.09s   # same three, without this branch's changes

$ python -m ruff check headroom/cli/wrap.py tests/test_cli/test_wrap_opencode.py
All checks passed!

$ python -m ruff format --check headroom/cli/wrap.py tests/test_cli/test_wrap_opencode.py
2 files already formatted

$ python -m mypy headroom
Success: no issues found in 523 source files

Real Behavior Proof

  • Environment: Windows 11, Python 3.11.9, headroom 0.35.0 (rust_core: loaded), with a stub opencode on PATH that curls the proxy's /health — so the wrapped agent reports the upstream the proxy actually resolved.
  • Exact command / steps: (1) baseline against released 0.35.0 without this fix on sys.pathpython -m headroom.cli wrap opencode --port 8799 --no-mcp --no-serena --openai-api-url https://api.deepseek.com/v1; (2) same command on this branch, port 8801; (3) env-var form on this branch — OPENAI_TARGET_API_URL=https://api.deepseek.com/v1 python -m headroom.cli wrap opencode --port 8802 --no-mcp --no-serena.
  • Observed result: step 1 reproduces the bug exactly — wrap prints Extra args: --openai-api-url https://api.deepseek.com/v1 (the flag is swallowed by ignore_unknown_options and handed to the agent) and the proxy it started reports "openai_api_url": null, i.e. it would forward to api.openai.com. Step 2 prints no Extra args line and the proxy reports "openai_api_url": "https://api.deepseek.com/v1". Step 3 reports the same. All three runs cleaned up their proxy on exit — no listener left on 8799/8801/8802.
  • Not tested: a live request against api.deepseek.com with a real key — none available here, so the proof stops at the proxy's resolved upstream; the forwarding itself is unchanged code already covered by the headroom proxy --openai-api-url path. The --copilot-subscription rejection was exercised as a unit test, not against a real Copilot subscription.

Runtime Rollout Safety

  • Rollout-managed feature(s): none — a CLI option and its plumbing, not gated by the rollout-channel machinery.
  • Minimum rollout channel: n/a, not rollout-gated.
  • Stable/default behavior changed: no — without the flag and without OPENAI_TARGET_API_URL, openai_api_url stays None and the proxy resolves its upstream exactly as before; a test pins this.
  • Kill switch / disable path: omit the flag and leave OPENAI_TARGET_API_URL unset — behavior is identical to the previous default.
  • Unsafe override required: no.
  • Qualification impact: none — no change to compression, routing, or the proxy request path; the option only chooses which upstream URL the proxy is started with.
  • Rollback path: revert this commit — no config, cache, or on-disk state is written by the new option, so a revert needs no cleanup.

Review Readiness

  • I have performed a self-review
  • This PR is ready for human review

Checklist

  • My code follows the project's style guidelines
  • 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
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I did not edit CHANGELOG.md — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this)

Screenshots (if applicable)

n/a — CLI and docs change.

Additional Notes

  • The x-headroom-base-url header workaround from the issue is deliberately left out of the DeepSeek guide. With --openai-api-url the proxy targets the right upstream directly, so the header — and its "wrap regenerates the provider block, so re-add it every run" caveat — is no longer needed for this case. It stays documented where it belongs, in configuration.mdx and pipeline-extensions.mdx, as a per-request override.
  • Scope is intentionally limited to wrap opencode, the command named in the issue. Other wrap subcommands that hardcode an upstream (mistral, kimi, grok) or detect one (zcode) are unaffected; giving the whole wrap family a uniform upstream override is a reasonable follow-up, not this fix.

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PR governance

This PR follows the template and is marked ready for human review.

@github-actions github-actions Bot added the status: needs author action Pull request body or readiness checklist still needs author updates label Aug 19, 2026
@codecov-commenter

codecov-commenter commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added status: ready for review Pull request body is complete and the author marked it ready for human review and removed status: needs author action Pull request body or readiness checklist still needs author updates labels Aug 19, 2026
`headroom wrap opencode` had no way to say where OpenAI-compatible traffic
should go. It only forwarded `openai_api_url` to the proxy for
`--copilot-subscription`; every other run inherited the proxy default,
`https://api.openai.com/v1`. Users following the README with a DeepSeek (or
Together/OpenRouter/self-hosted) key had their key presented to OpenAI and
rejected with `401 Incorrect API key provided`, pointing them at a
platform.openai.com dashboard for a key they never had.

`opencode` now takes `--openai-api-url` and honors `OPENAI_TARGET_API_URL`,
mirroring `headroom proxy`, and passes it through to `_ensure_proxy`. It is
rejected alongside `--copilot-subscription`, which resolves its own upstream.

Docs: the OpenCode + DeepSeek guide gains a "Incorrect API key provided"
entry and a "when is wrap safe?" section reconciling its "do not use
`headroom wrap`" wording with the README's wrap table, and the README table
gains a footnote on the default upstream.

Fixes headroomlabs-ai#3107
@Ayushraj06-bit
Ayushraj06-bit force-pushed the fix/wrap-opencode-openai-api-url-3107 branch from e0cfeba to dce9aeb Compare August 19, 2026 10:47

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the latest CLI plumbing, conflict validation, launch ordering, tests, and documentation. The override reaches the existing _ensure_proxy upstream/mismatch path, remains unset by default, supports the proxy's canonical env var, and fails before mutations when combined with subscription routing. The docs accurately distinguish generated wrap configuration from manually pinned model metadata. The branch is mergeable and checks are green. No blocking findings.

@Parideboy

Copy link
Copy Markdown
Contributor

This fixes the same bug I was chasing in #3110 (closing that one in favor of this — it's more complete: OPENAI_TARGET_API_URL env var, broader docs, and it's already approved/green).

One gap: this PR doesn't touch docs/content/docs/opencode.mdx, the canonical OpenCode integration page. #3110 added a short note there documenting --openai-api-url in the Options block with a cross-link to the DeepSeek guide. Might be worth folding in so the canonical page doesn't lag behind opencode-deepseek.mdx and the README. The hunk from #3110, for reference:

--- a/docs/content/docs/opencode.mdx
+++ b/docs/content/docs/opencode.mdx
@@ -41,6 +41,7 @@
   --no-serena \
   --code-graph \
   --no-proxy \
+  --openai-api-url https://api.deepseek.com/v1 \
   --learn \
   --memory \
   --backend anthropic \
@@ -49,6 +50,13 @@
   -- <opencode args>

+Pass --openai-api-url when OpenCode should route through a third-party
+OpenAI-compatible provider instead of OpenAI itself — for example DeepSeek's
+https://api.deepseek.com/v1. Without it, OpenAI-compatible traffic is
+forwarded to OpenAI's own API by default, and a non-OpenAI API key is
+rejected with an HTTP 401 error. See
+OpenCode + DeepSeek for a full walkthrough.
+

Provider Model Mapping


Not blocking — just didn't want it to get lost since the two PRs overlapped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: ready for review Pull request body is complete and the author marked it ready for human review

Projects

None yet

4 participants