feat: add opencode config + sync.py (LiteLLM + MCP gateway)

This commit is contained in:
2026-05-21 08:03:41 +02:00
parent 8a3d94bfdc
commit 7d33a89a15
3 changed files with 208 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""
Regenerate ~/.config/opencode/config.json from live nuclide.systems state.
Queries:
- LiteLLM http://192.168.1.40:14000/v1/models → available models
- MCP GW https://mcp.nuclide.systems/servers → registered server groups
Run manually or wire into dotfiles install / Coder startup script.
Requires: MCP_GATEWAY_TOKEN env var (get via https://mcp.nuclide.systems/mcp-config
or set from GATEWAY_API_KEYS in .env)
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
# ── config ────────────────────────────────────────────────────────────────────
LITELLM_BASE = os.environ.get("OPENAI_BASE_URL", "http://192.168.1.40:14000/v1")
LITELLM_KEY = os.environ.get("OPENAI_API_KEY", "sk-tapirnase")
MCP_BASE = "https://mcp.nuclide.systems"
CONFIG_PATH = Path.home() / ".config" / "opencode" / "config.json"
# Preferred model order for the default picker
PREFERRED_CODER = ["qwen3-coder-30b-a3b-instruct", "openai-gpt-oss-120b", "gemini-2.5-pro"]
PREFERRED_SMALL = ["gemini-2.5-flash-lite", "mistral-small-latest", "gemini-2.5-flash"]
# ── helpers ───────────────────────────────────────────────────────────────────
def _get(url: str, token: str | None = None, api_key: str | None = None) -> dict | list | None:
try:
import urllib.request, urllib.error
req = urllib.request.Request(url)
if token:
req.add_header("Authorization", f"Bearer {token}")
if api_key:
req.add_header("Authorization", f"Bearer {api_key}")
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read())
except Exception as e:
print(f" warn: {url}: {e}", file=sys.stderr)
return None
def pick_model(available: set[str], preferences: list[str]) -> str | None:
for name in preferences:
if name in available:
return f"openai/{name}"
return None
# ── main ──────────────────────────────────────────────────────────────────────
def main() -> None:
print("opencode sync: fetching live state…")
# 1. LiteLLM models
models_resp = _get(f"{LITELLM_BASE}/models", api_key=LITELLM_KEY)
available: set[str] = set()
if models_resp and "data" in models_resp:
available = {m["id"] for m in models_resp["data"]}
print(f" LiteLLM: {len(available)} models")
else:
print(" LiteLLM: unreachable, using fallback models")
default_model = pick_model(available, PREFERRED_CODER) or "openai/qwen3-coder-30b-a3b-instruct"
small_model = pick_model(available, PREFERRED_SMALL) or "openai/gemini-2.5-flash-lite"
print(f" default: {default_model}")
print(f" small: {small_model}")
# 2. MCP gateway groups
gw_token = os.environ.get("MCP_GATEWAY_TOKEN", "")
servers_resp = _get(f"{MCP_BASE}/servers", token=gw_token)
groups: set[str] = set()
if isinstance(servers_resp, list):
for s in servers_resp:
if isinstance(s, dict):
grp = s.get("group")
if grp:
groups.add(grp)
elif isinstance(servers_resp, dict):
for name, cfg in servers_resp.items():
grp = cfg.get("group") if isinstance(cfg, dict) else None
if grp:
groups.add(grp)
if not groups:
# fallback: static known groups
groups = {"dev", "research", "personal", "image", "storage"}
print(f" MCP GW: unreachable, using static groups: {sorted(groups)}")
else:
print(f" MCP GW: groups = {sorted(groups)}")
mcp_entries: dict = {}
for group in sorted(groups):
mcp_entries[f"nuclide-{group}"] = {
"type": "remote",
"url": f"{MCP_BASE}/group/{group}/mcp",
"enabled": True,
"headers": {"Authorization": "Bearer {env:MCP_GATEWAY_TOKEN}"},
}
# 3. Build config
cfg = {
"$schema": "https://opencode.ai/config.json",
"model": default_model,
"small_model": small_model,
"provider": {
"openai": {
"name": "nuclide-litellm",
"options": {
"apiKey": "{env:OPENAI_API_KEY}",
"baseURL": "{env:OPENAI_BASE_URL}",
"timeout": 600000,
"chunkTimeout": 60000,
},
},
"anthropic": {
"options": {
"apiKey": "{env:ANTHROPIC_API_KEY}",
"baseURL": "{env:ANTHROPIC_BASE_URL}",
"timeout": 600000,
},
},
},
"mcp": mcp_entries,
}
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
CONFIG_PATH.write_text(json.dumps(cfg, indent=2) + "\n")
print(f" written: {CONFIG_PATH}")
if __name__ == "__main__":
main()