From e5d4e252defd5d722616ef7451b5516e018fe2fd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Aug 2026 19:13:06 +0000 Subject: [PATCH] feat(opencode): multi-provider support with local provider for qwen3.8-27b --- opencode.json | 17 ++++++++++++++ pilot/opencode_review.py | 48 ++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/opencode.json b/opencode.json index 4c9b059..38cf975 100644 --- a/opencode.json +++ b/opencode.json @@ -20,6 +20,23 @@ } } } + }, + "local": { + "npm": "@ai-sdk/openai-compatible", + "name": "Local AI workstation (qwen3.8-27b)", + "options": { + "baseURL": "http://192.168.1.79:18020/v1", + "apiKey": "PLACEHOLDER_REPLACED_AT_RUNTIME" + }, + "models": { + "qwen3.8-27b": { + "name": "Qwen 3.8 27B (local)", + "limit": { + "context": 32768, + "output": 8192 + } + } + } } }, "lsp": {}, diff --git a/pilot/opencode_review.py b/pilot/opencode_review.py index 48b9923..990e8b5 100644 --- a/pilot/opencode_review.py +++ b/pilot/opencode_review.py @@ -389,12 +389,26 @@ def sanitize_workdir(workdir: str) -> list[str]: def install_config(src: str, dst: str) -> bool: - """Copy `opencode.json` from src to dst, substituting the model endpoint. + """Copy `opencode.json` from src to dst, substituting per-provider endpoint + + API key. - The committed `opencode.json` carries a neutral placeholder for the model - provider's `baseURL`, so the repo can be public without publishing the - address of a private network. The real endpoint is supplied at runtime by - `PRAGENT_MODEL_BASE_URL` and patched in here. + The committed `opencode.json` carries neutral placeholders for every + provider's `baseURL`/`apiKey` so the repo can be public without leaking + private-network addresses. Real values are supplied at runtime and patched + in here. + + Env var convention (case-sensitive provider name — `headroom`, `local`): + + PRAGENT__BASE_URL — per-provider endpoint override + PRAGENT__API_KEY — per-provider API key override + PRAGENT_MODEL_BASE_URL — legacy catchall, applies to every provider + when the per-provider var is unset + 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. This is done in Python rather than with opencode's own `{env:VAR}` config templating because the reviewer subprocess runs with an allow-listed @@ -405,20 +419,26 @@ def install_config(src: str, dst: str) -> bool: """ if not os.path.isfile(src): return False - base_url = os.environ.get("PRAGENT_MODEL_BASE_URL", "").strip() - api_key = os.environ.get("PRAGENT_MODEL_API_KEY", "").strip() - if not base_url and not api_key: + default_url = os.environ.get("PRAGENT_MODEL_BASE_URL", "").strip() + default_key = os.environ.get("PRAGENT_MODEL_API_KEY", "").strip() + if not default_url and not default_key: + # Fast path: no env at all → commit copy is fine, no rewrite needed. shutil.copy2(src, dst) return True try: with open(src, encoding="utf-8") as f: cfg = json.load(f) - for prov in (cfg.get("provider") or {}).values(): - if isinstance(prov, dict) and isinstance(prov.get("options"), dict): - if base_url: - prov["options"]["baseURL"] = base_url - if api_key: - prov["options"]["apiKey"] = api_key + for name, prov in (cfg.get("provider") or {}).items(): + if not isinstance(prov, dict) or not isinstance(prov.get("options"), dict): + continue + per_url = os.environ.get(f"PRAGENT_{name.upper()}_BASE_URL", "").strip() + per_key = os.environ.get(f"PRAGENT_{name.upper()}_API_KEY", "").strip() + url = per_url or default_url + key = per_key or default_key + if url: + prov["options"]["baseURL"] = url + if key: + prov["options"]["apiKey"] = key with open(dst, "w", encoding="utf-8") as f: json.dump(cfg, f, indent=2) except (OSError, ValueError, AttributeError):