fix(wrap): let wrap opencode target a third-party OpenAI-compatible upstream - #3125
Conversation
PR governanceThis PR follows the template and is marked ready for human review. |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
`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
e0cfeba to
dce9aeb
Compare
JerrettDavis
left a comment
There was a problem hiding this comment.
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.
|
This fixes the same bug I was chasing in #3110 (closing that one in favor of this — it's more complete: One gap: this PR doesn't touch --- 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 Provider Model Mapping |
Description
headroom wrap opencodehad no way to say where OpenAI-compatible traffic should go.opencode()forwardedopenai_api_urlto_ensure_proxyonly 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 with401 Incorrect API key providedand pointed them at aplatform.openai.comdashboard for a key they never had. Meanwhiledocs/content/docs/opencode-deepseek.mdxsaid "Do not useheadroom wrap", with nothing reconciling the two.This adds the missing plumbing (
--openai-api-url/OPENAI_TARGET_API_URL, mirroringheadroom proxy) and the documentation the issue asked for.Closes #3107
Type of Change
Changes Made
headroom/cli/wrap.py:wrap opencodegains--openai-api-url, withenvvar="OPENAI_TARGET_API_URL", mirroringheadroom 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-urlwith--copilot-subscriptionraises aClickExceptionbefore anything is mutated — the subscription resolves its own upstream, so a manual override would silently fight it.headroom/cli/wrap.py: theopencodedocstring documents the default upstream and both invocation forms.docs/content/docs/opencode-deepseek.mdx: new "Common issues" entry for theIncorrect API key provided401 (it comes from OpenAI, not DeepSeek) and a "When isheadroom wrap opencodesafe?" section — safe as-is for the default OpenAI/Anthropic upstreams, needs--openai-api-urlfor 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 useheadroom 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-subscriptionrejection.Testing
pytest)ruff check .)mypy headroom)Three failures in the wider
tests/test_clirun are pre-existing and unrelated — they reproduce identically on a cleanmain(git stash) and are Windows-specific symlink/settings-path issues intest_recover_codex.pyandtest_unwrap_claude.py.Test Output
Real Behavior Proof
rust_core: loaded), with a stubopencodeonPATHthat curls the proxy's/health— so the wrapped agent reports the upstream the proxy actually resolved.sys.path—python -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.Extra args: --openai-api-url https://api.deepseek.com/v1(the flag is swallowed byignore_unknown_optionsand handed to the agent) and the proxy it started reports"openai_api_url": null, i.e. it would forward toapi.openai.com. Step 2 prints noExtra argsline 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.api.deepseek.comwith 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 theheadroom proxy --openai-api-urlpath. The--copilot-subscriptionrejection was exercised as a unit test, not against a real Copilot subscription.Runtime Rollout Safety
OPENAI_TARGET_API_URL,openai_api_urlstaysNoneand the proxy resolves its upstream exactly as before; a test pins this.OPENAI_TARGET_API_URLunset — behavior is identical to the previous default.Review Readiness
Checklist
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
x-headroom-base-urlheader workaround from the issue is deliberately left out of the DeepSeek guide. With--openai-api-urlthe 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, inconfiguration.mdxandpipeline-extensions.mdx, as a per-request override.wrap opencode, the command named in the issue. Otherwrapsubcommands 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.