feat(opencode): configure vllm-qwen38 provider for local Qwen3.8-27B

This commit is contained in:
Claude
2026-08-24 22:49:50 +00:00
parent 317942e15b
commit 51b81def98
6 changed files with 39 additions and 22 deletions
+2
View File
@@ -8,3 +8,5 @@ dist/
__pycache__/
*.pyc
.worktrees/
.claude/
.opencode/package-lock.json
+18 -5
View File
@@ -21,19 +21,32 @@
}
}
},
"local": {
"vllm-qwen38": {
"npm": "@ai-sdk/openai-compatible",
"name": "Local AI workstation (qwen3.8-27b)",
"name": "Qwen3.8-27B vLLM (RTX 3090, MTP spec-decode)",
"options": {
"baseURL": "http://192.168.1.79:18020/v1",
"apiKey": "PLACEHOLDER_REPLACED_AT_RUNTIME"
"apiKey": "PLACEHOLDER_REPLACED_AT_RUNTIME",
"timeout": 300000,
"chunkTimeout": 30000
},
"models": {
"qwen3.8-27b": {
"name": "Qwen 3.8 27B (local)",
"name": "Qwen3.8-27B (vLLM, MTP, 150k ctx)",
"tools": true,
"thinking": true,
"attachments": false,
"limit": {
"context": 57344,
"context": 150000,
"output": 8192
},
"options": {
"temperature": 0.3,
"topP": 0.8,
"topK": 20,
"repetitionPenalty": 1.05,
"frequencyPenalty": 0,
"presencePenalty": 0
}
}
}
+3 -3
View File
@@ -489,7 +489,7 @@ def _resolve_display_model(base_model: str, config: dict | None) -> str:
like `claude-sonnet-5` or `qwen3.8-27b` is safe. Re-prefixed with
the model's `provider` field from `cost_model.Price` (default
`headroom`) so the opencode subprocess routes correctly — e.g.
`qwen3.8-27b` → `local/qwen3.8-27b` (local AI workstation on
`qwen3.8-27b` → `vllm-qwen38/qwen3.8-27b` (vLLM on RTX 3090 at
192.168.1.79:18020), `claude-sonnet-5` → `headroom/claude-sonnet-5`
(Anthropic pricing proxy).
3. Default — `f"headroom/{base_model}"` where `base_model` is the bare
@@ -505,8 +505,8 @@ def _resolve_display_model(base_model: str, config: dict | None) -> str:
cfg_model = (config or {}).get("model")
if isinstance(cfg_model, str) and cfg_model.strip():
# Look up the provider from PRICES so the opencode subprocess routes
# through the right provider block (local vs headroom). Lazy import —
# the ollama path doesn't touch cost_model.
# through the right provider block (vllm-qwen38 vs headroom). Lazy
# import — the ollama path doesn't touch cost_model.
from cost_model import PRICES
provider = PRICES.get(cfg_model.strip())
if provider is not None:
+8 -7
View File
@@ -57,7 +57,7 @@ class Price:
"""Per-MTok prices. `cache_write` and `cache_read` are absolute rates, not
multipliers, so providers with different cache economics stay comparable.
`provider` is the opencode provider name (`headroom`, `local`, ...). It
`provider` is the opencode provider name (`headroom`, `vllm-qwen38`, ...). It
doubles as the dispatch key for `.pr-review.json:model` overrides — when
a per-repo override is set, `_resolve_display_model` returns
`f"{provider}/{key}"` so the opencode subprocess routes correctly.
@@ -98,12 +98,13 @@ PRICES: dict[str, Price] = {
# xAI Grok — cache_write = input
"grok-4.5": Price("Grok 4.5", 2.00, 6.00, 2.00, 0.30),
"grok-4.3": Price("Grok 4.3", 1.25, 2.50, 1.25, 0.20),
# Self-hosted — local AI workstation, no per-token charge. provider="local"
# so the opencode subprocess routes via the `local` provider block in
# opencode.json (baseURL=http://192.168.1.79:18020/v1). Equivalent-cost
# column will read $0 — the cost-comparison signal is that the same work
# would bill $X on a paid model.
"qwen3.8-27b": Price("Qwen 3.8 27B (local)", 0.0, 0.0, 0.0, 0.0, provider="local"),
# Self-hosted — AI workstation RTX 3090, vLLM + DFlash2 spec-decode, no
# per-token charge. provider="vllm-qwen38" so the opencode subprocess
# routes via the matching provider block in opencode.json
# (baseURL=http://192.168.1.79:18020/v1). Equivalent-cost column reads $0
# — the cost-comparison signal is that the same work would bill $X on a
# paid model.
"qwen3.8-27b": Price("Qwen3.8-27B (vLLM, MTP, 150k ctx)", 0.0, 0.0, 0.0, 0.0, provider="vllm-qwen38"),
}
+4 -4
View File
@@ -397,7 +397,7 @@ def install_config(src: str, dst: str) -> bool:
private-network addresses. Real values are supplied at runtime and patched
in here.
Env var convention (case-sensitive provider name — `headroom`, `local`):
Env var convention (case-sensitive provider name — `headroom`, `vllm-qwen38`):
PRAGENT_<NAME>_BASE_URL — per-provider endpoint override
PRAGENT_<NAME>_API_KEY — per-provider API key override
@@ -406,9 +406,9 @@ def install_config(src: str, dst: str) -> bool:
PRAGENT_MODEL_API_KEY — legacy catchall (same)
Per-provider wins over the catchall. The first 2 win when the operator
needs a different endpoint per upstream (e.g. headroom → MiniMax, local →
ai-workstation). The catchall keeps the single-provider deploys from
needing any env config.
needs a different endpoint per upstream (e.g. headroom → MiniMax,
vllm-qwen38 → ai-workstation). The catchall keeps the single-provider
deploys from needing any env config.
This is done in Python rather than with opencode's own `{env:VAR}` config
templating because the reviewer subprocess runs with an allow-listed
+4 -3
View File
@@ -462,11 +462,12 @@ def test_resolve_display_model_precedence(monkeypatch):
ai_review._resolve_display_model("MiniMax-M2.7", {"model": "claude-sonnet-5"})
== "headroom/claude-sonnet-5"
)
# Self-hosted models carry provider="local" → routes to the `local`
# provider block in opencode.json (AI workstation on 192.168.1.79:18020).
# Self-hosted models carry provider="vllm-qwen38" → routes to the
# matching provider block in opencode.json (AI workstation on
# 192.168.1.79:18020).
assert (
ai_review._resolve_display_model("MiniMax-M2.7", {"model": "qwen3.8-27b"})
== "local/qwen3.8-27b"
== "vllm-qwen38/qwen3.8-27b"
)
# 3. Env wins over config
monkeypatch.setenv("OPENCODE_MODEL", "headroom/MiniMax-M2.7")