feat: add opencode config + sync.py (LiteLLM + MCP gateway)
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user