136 lines
5.2 KiB
Python
136 lines
5.2 KiB
Python
#!/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()
|