mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(opencode): keep Claude models off OpenAI provider
## Description The injected `headroom` OpenCode provider uses `@ai-sdk/openai-compatible` and the proxy's `/v1/chat/completions` route. It currently advertises Claude model IDs in that provider, so OpenCode sends Claude requests to the OpenAI upstream and receives `invalid_api_key` errors. Keep Claude on OpenCode's native `anthropic` provider, which Headroom already redirects to the proxy. Closes #2911 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (bug fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Remove Claude IDs from the injected OpenAI-compatible provider model map. - Keep GPT models available through the `headroom/<id>` namespace. - Add regression assertions that generated config never advertises Claude models on this endpoint. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text python -m pytest tests/test_providers_opencode_config.py -q -k "not build_launch_env_with_project" 40 passed, 1 deselected python -m ruff check headroom/providers/opencode/config.py tests/test_providers_opencode_config.py All checks passed! python -m compileall -q headroom/providers/opencode/config.py tests/test_providers_opencode_config.py (pass) ``` The full config test module also exposes an unrelated pre-existing Windows path assertion failure in `test_build_launch_env_with_project`; the failure is caused by comparing a native `Path` string with JSON-escaped backslashes and is outside this change. ## Real Behavior Proof - Environment: Windows 11, Python 3.11; no external API credentials used. - Exact command / steps: `python -c "from headroom.providers.opencode.config import headroom_provider_entry; print(sorted(headroom_provider_entry(8787)['models']))"` - Observed result: `['gpt-4.1', 'gpt-4o']`; the generated OpenAI-compatible provider no longer advertises any `claude-*` IDs. - Not tested: live OpenCode request routing or a vendor API call, because they require external credentials. The regression suite verifies the generated configuration consumed by OpenCode. ## 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 (not needed; the provider routing rationale is documented inline) - [ ] I have made corresponding changes to the documentation (the generated provider behavior is documented in code; existing docs describe the separate npm provider) - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective - [x] New and existing relevant unit tests pass locally with my changes - [x] I did **not** edit `CHANGELOG.md` ## Additional Notes The native `anthropic` and `openai` provider entries both continue to point at the Headroom proxy, so this change only removes an invalid duplicate Claude route and does not affect native Claude traffic.
This commit is contained in:
parent
702dbc5902
commit
5e53b8aa0a
2 changed files with 19 additions and 20 deletions
|
|
@ -31,24 +31,18 @@ _MCP_BLOCK_RE = re.compile(
|
|||
)
|
||||
HEADROOM_OPENCODE_PLUGIN = "headroom-opencode"
|
||||
|
||||
# Models exposed by the injected `headroom` provider. OpenCode only resolves
|
||||
# `headroom/<id>` for ids listed in the provider's `models` map, so an empty
|
||||
# map means every documented `headroom/*` model fails with "Model not found".
|
||||
# Keep in sync with DEFAULT_MODELS in plugins/opencode/src/provider.ts and the
|
||||
# table in plugins/opencode/README.md.
|
||||
# Models exposed by the injected `headroom` provider. This provider uses
|
||||
# ``@ai-sdk/openai-compatible`` and the proxy's ``/v1/chat/completions`` path,
|
||||
# which is routed to the configured OpenAI upstream. Do not advertise Claude
|
||||
# models here: OpenCode would send them through the OpenAI endpoint and report
|
||||
# an ``invalid_api_key`` error instead of reaching Anthropic. Claude models are
|
||||
# available through OpenCode's native ``anthropic`` provider, whose base URL is
|
||||
# also redirected to Headroom by ``build_opencode_config_content``.
|
||||
#
|
||||
# OpenCode only resolves ``headroom/<id>`` for ids listed in this map, so an
|
||||
# empty map means every documented ``headroom/*`` model fails with "Model not
|
||||
# found".
|
||||
HEADROOM_OPENCODE_MODELS: dict[str, Any] = {
|
||||
"claude-sonnet-4-6": {
|
||||
"name": "Claude Sonnet 4.6",
|
||||
"limit": {"context": 200000, "output": 16384},
|
||||
},
|
||||
"claude-opus-4-6": {
|
||||
"name": "Claude Opus 4.6",
|
||||
"limit": {"context": 200000, "output": 16384},
|
||||
},
|
||||
"claude-haiku-4-5-20251001": {
|
||||
"name": "Claude Haiku 4.5",
|
||||
"limit": {"context": 200000, "output": 8192},
|
||||
},
|
||||
"gpt-4o": {
|
||||
"name": "GPT-4o",
|
||||
"limit": {"context": 128000, "output": 16384},
|
||||
|
|
|
|||
|
|
@ -199,7 +199,10 @@ def test_inject_provider_config_creates_file(
|
|||
assert config["provider"]["headroom"]["npm"] == "@ai-sdk/openai-compatible"
|
||||
# Bare model ids: OpenCode resolves them as "headroom/<id>" (#1657).
|
||||
models = config["provider"]["headroom"]["models"]
|
||||
assert "claude-sonnet-4-6" in models
|
||||
assert set(models) == {"gpt-4o", "gpt-4.1"}
|
||||
# The injected provider is OpenAI-compatible. Claude models must remain on
|
||||
# OpenCode's native Anthropic provider so they are not sent to OpenAI.
|
||||
assert not any(model_id.startswith("claude-") for model_id in models)
|
||||
assert all(not model_id.startswith("headroom/") for model_id in models)
|
||||
assert "mcp" not in config
|
||||
assert "model" not in config # headroom provider is a transparent pass-through
|
||||
|
|
@ -456,10 +459,12 @@ def test_build_opencode_config_content_without_mcp(
|
|||
providers = config["provider"]
|
||||
assert providers["anthropic"]["options"]["baseURL"] == "http://127.0.0.1:8787/v1"
|
||||
assert providers["openai"]["options"]["baseURL"] == "http://127.0.0.1:8787/v1"
|
||||
# The headroom provider exposes explicit models so "headroom/<id>" resolves (#1657).
|
||||
# The headroom provider exposes only models supported by its
|
||||
# OpenAI-compatible endpoint so "headroom/<id>" resolves safely (#1657).
|
||||
assert providers["headroom"]["options"]["baseURL"] == "http://127.0.0.1:8787/v1"
|
||||
models = providers["headroom"]["models"]
|
||||
assert "claude-sonnet-4-6" in models
|
||||
assert set(models) == {"gpt-4o", "gpt-4.1"}
|
||||
assert not any(model_id.startswith("claude-") for model_id in models)
|
||||
assert all(not model_id.startswith("headroom/") for model_id in models)
|
||||
# The transport plugin is injected by absolute path (opencode loads it directly).
|
||||
assert config["plugin"] == [str(plugin)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue