c1fac1e688
Beseitigt Redundanzen/Mismatches, die sich mit den Lanes angesammelt hatten: - Modell-Manager: großen animierten SVG-Gateway-Graph (~310 Z.) entfernt — Rolle→Modell deckt das Slot-Grid ab, Lanes der Lane-Editor, IDE-Config die "Verbinden"-Seite. Cockpit-Brain-Switch raus → Verweis auf Hermes-Tab. - Hermes: zweiten SVG-Graph + 4 Status-Kacheln durch einen ruhigen Box→Pfeil- Fluss ersetzt (Terminal → Gateway → Hirn/Verdrahtung/PC), Stil wie "Verbinden". - Hirn-Wechsel vereinheitlicht: nur noch im Hermes-Tab. Installiert = warm-bewusst (/api/agent/brain/set), Alias = /api/agent/brain; Lane-Aliase chat/coding/fast/heavy. - Terminologie auf Lane-Sprache: ConnectView "model auto" → chat/coding; connect.py-Snippets defaulten auf 'coding' (GATEWAY_MODELS mit Lanes vorn). - Zentrale: ActiveModelsCard + RolesCard zu einer ModelsCard verschmolzen (Rollen + warm + Inferenz + Größe); Layout entdoppelt. ~600 Zeilen SVG-Graph-Code raus, 2 tote Karten gelöscht. Verifiziert: npm run build (tsc strict) clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
161 lines
6.4 KiB
Python
161 lines
6.4 KiB
Python
"""
|
|
Connect: erzeugt saubere, getestete Konfig-Snippets für IDEs/Agenten auf dem
|
|
LOKALEN PC (separate Maschine im LAN). Alle zeigen auf den **Gateway** der Box
|
|
(Lanes `coding`/`chat`, Cockpit-Port :9001/v1) + den **Shared-Memory-MCP** (MC :9001).
|
|
|
|
Wichtig: Host ist die LAN-IP der Box (NICHT eine Proxy-Domain) — das war in v1
|
|
die häufigste Fehlerquelle. Der Aufrufer übergibt den Host explizit.
|
|
"""
|
|
|
|
import json
|
|
|
|
import httpx
|
|
|
|
from config import LLAMA_SWAP_URL, MEM0_SERVICE_URL, PORT
|
|
|
|
DEFAULT_HOST = "192.168.178.151"
|
|
|
|
# Modelle/Lanes, die der Gateway anbietet. Lanes zuerst: 'coding' (agentischer Coder) ist der
|
|
# Standard für IDEs, 'chat' für Allgemeines; dahinter die direkten Aliase.
|
|
GATEWAY_MODELS = ["coding", "chat", "fast", "heavy", "coder", "vision"]
|
|
|
|
|
|
def _gw(host: str) -> str:
|
|
# Eingebauter Gateway: MC2 serviert /v1 selbst (gleicher Port wie das Cockpit).
|
|
return f"http://{host}:{PORT}/v1"
|
|
|
|
|
|
def build_snippets(host: str = DEFAULT_HOST,
|
|
mcp_script_path: str = r"F:\\Coding Stuff\\mission-control-2\\mcp\\mcp_memory.py",
|
|
mcp_python: str = "python") -> dict:
|
|
gw = _gw(host)
|
|
mc_url = f"http://{host}:{PORT}"
|
|
|
|
cline = json.dumps({
|
|
"apiProvider": "openai",
|
|
"openAiBaseUrl": gw,
|
|
"openAiApiKey": "local",
|
|
"openAiModelId": "coding",
|
|
}, indent=2)
|
|
|
|
opencode = json.dumps({
|
|
"provider": {
|
|
"bosgame": {
|
|
"npm": "@ai-sdk/openai-compatible",
|
|
"name": "Bosgame Gateway",
|
|
"options": {"baseURL": gw, "apiKey": "local"},
|
|
"models": {m: {"name": m} for m in GATEWAY_MODELS},
|
|
}
|
|
}
|
|
}, indent=2)
|
|
|
|
cursor = json.dumps({
|
|
"Base URL": gw,
|
|
"API Key": "local",
|
|
"Active Model": "coding"
|
|
}, indent=2)
|
|
|
|
zed = json.dumps({
|
|
"language_models": {
|
|
"openai_compatible": {
|
|
"bosgame": {
|
|
"api_url": gw,
|
|
"available_models": [
|
|
{"name": m, "display_name": m, "max_tokens": 131072,
|
|
"capabilities": {"tools": True}} for m in GATEWAY_MODELS
|
|
],
|
|
}
|
|
}
|
|
},
|
|
"assistant": {
|
|
"default_model": {
|
|
"provider": "openai_compatible",
|
|
"model": "coding"
|
|
}
|
|
}
|
|
}, indent=2)
|
|
|
|
cont = json.dumps({
|
|
"models": [
|
|
{"title": f"Bosgame / {m}", "provider": "openai", "model": m,
|
|
"apiBase": gw, "apiKey": "local"} for m in ("coding", "chat", "coder")
|
|
]
|
|
}, indent=2)
|
|
|
|
# Claude Code spricht das Anthropic-Format; der Gateway ist OpenAI-kompatibel und
|
|
# bietet KEIN /v1/messages (verifiziert). Daher braucht es einen kleinen Übersetzer
|
|
# (Anthropic ⇄ OpenAI) als Aufsatz. Die env-Vars sind Claude Codes echte Schnittstelle.
|
|
claude_code = (
|
|
f"# Claude Code spricht das Anthropic-Format — der Gateway ist OpenAI-kompatibel ({gw})\n"
|
|
f"# und hat kein /v1/messages. Dazwischen muss ein Übersetzer (Anthropic ⇄ OpenAI) laufen:\n"
|
|
f"# • claude-code-router (leichtgewichtig, npm)\n"
|
|
f"# • oder LiteLLM mit /v1/messages-Bridge\n"
|
|
f"# Den Übersetzer auf den Gateway zeigen lassen: baseURL={gw}, model=coding, apiKey=local.\n"
|
|
f"# Dann Claude Code auf den lokalen Übersetzer richten (Beispiel-Port 3456):\n"
|
|
f"\n"
|
|
f'export ANTHROPIC_BASE_URL="http://localhost:3456"\n'
|
|
f'export ANTHROPIC_AUTH_TOKEN="local"\n'
|
|
f'export ANTHROPIC_MODEL="coding"'
|
|
)
|
|
|
|
memory_mcp = json.dumps({
|
|
"mcpServers": {
|
|
"mission-control-memory": {
|
|
"command": mcp_python,
|
|
"args": [mcp_script_path],
|
|
"env": {"MC_URL": mc_url},
|
|
}
|
|
}
|
|
}, indent=2)
|
|
|
|
return {
|
|
"host": host,
|
|
"gateway_url": gw,
|
|
"mc_url": mc_url,
|
|
# Leitung 1 — das MODELL. Alle Snippets zeigen auf den OpenAI-kompatiblen Gateway.
|
|
"tools": {
|
|
"cline": {"label": "Roo Code / Cline", "lang": "json", "snippet": cline,
|
|
"note": "OpenAI-Provider → Gateway. Lane 'coding' (agentischer Coder); 'chat' für Allgemeines."},
|
|
"cursor": {"label": "Cursor", "lang": "json", "snippet": cursor,
|
|
"note": "Einstellungen ➔ Models ➔ OpenAI API key + Base URL."},
|
|
"opencode": {"label": "OpenCode", "lang": "jsonc", "snippet": opencode,
|
|
"note": "Datei opencode.jsonc, Key 'provider'."},
|
|
"zed": {"label": "Zed", "lang": "json", "snippet": zed,
|
|
"note": "settings.json → language_models.openai_compatible."},
|
|
"continue": {"label": "Continue", "lang": "json", "snippet": cont,
|
|
"note": "~/.continue/config.json (oder config.yaml mit identischen Keys)."},
|
|
"claude_code": {"label": "Claude Code", "lang": "bash", "snippet": claude_code,
|
|
"note": "Braucht einen Anthropic⇄OpenAI-Übersetzer vor dem Gateway."},
|
|
},
|
|
# Leitung 2 — das GEDÄCHTNIS. Separater MCP-Server, gilt zusätzlich zu jedem Tool oben.
|
|
"memory": {"label": "Shared Memory (MCP)", "lang": "json", "snippet": memory_mcp,
|
|
"note": "Eigene Leitung: MCP-Block für jedes MCP-fähige Tool. mcp_memory.py muss lokal liegen."},
|
|
}
|
|
|
|
|
|
def check_health() -> dict:
|
|
"""Live-Erreichbarkeit der beiden Leitungen, aus Sicht der Box:
|
|
Leitung 1 = Gateway/Engine (llama-swap), Leitung 2 = Gedächtnis-Sidecar (Mem0)."""
|
|
gateway = {"ok": False, "detail": "nicht erreichbar"}
|
|
try:
|
|
with httpx.Client(timeout=3.0) as c:
|
|
r = c.get(f"{LLAMA_SWAP_URL}/v1/models")
|
|
if r.status_code == 200:
|
|
n = len(r.json().get("data", []))
|
|
gateway = {"ok": True, "detail": f"{n} Modelle verfügbar" if n else "bereit"}
|
|
else:
|
|
gateway = {"ok": False, "detail": f"HTTP {r.status_code}"}
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
|
|
memory = {"ok": False, "detail": "nicht erreichbar"}
|
|
try:
|
|
with httpx.Client(timeout=3.0) as c:
|
|
r = c.get(f"{MEM0_SERVICE_URL}/health")
|
|
memory = ({"ok": True, "detail": "bereit"} if r.status_code == 200
|
|
else {"ok": False, "detail": f"HTTP {r.status_code}"})
|
|
except Exception: # noqa: BLE001
|
|
pass
|
|
|
|
return {"gateway": gateway, "memory": memory}
|