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