Download naldo_opencode_bridge.py from Erinaldordso/Minecraft-Friends-AI-ZeroGPU: direct link, hf CLI and curl.
- Browser
- Download file 13.7 kB
-
https://huggingface.co/spaces/Erinaldordso/Minecraft-Friends-AI-ZeroGPU/resolve/main/naldo_opencode_bridge.py
- Command line
-
hf download hf://spaces/Erinaldordso/Minecraft-Friends-AI-ZeroGPU/naldo_opencode_bridge.py
-
curl -L -o naldo_opencode_bridge.py https://huggingface.co/spaces/Erinaldordso/Minecraft-Friends-AI-ZeroGPU/resolve/main/naldo_opencode_bridge.py
13.7 kB
| """OpenAI-compatible bridge for Naldo_Bot backed exclusively by OpenCode. | |
| Listens on 127.0.0.1 only. Translates POST /v1/chat/completions into the real | |
| OpenCode session API discovered via /doc: | |
| - POST /session -> create session (body may carry title) | |
| - POST /session/{sessionID}/message -> send prompt, wait for reply | |
| - GET /global/health -> opencode health | |
| Each inference creates a fresh session (isolated per Voyager call) and sends | |
| ``tools: {}`` on the message. OpenCode 1.18.33 was verified to return no tool | |
| parts and perform no requested bash side effect in this mode. Custom deny-all | |
| session permissions are not used because Zen free-tier rejects them with 403. | |
| Only assistant ``text`` parts are returned. | |
| Temperature: OpenCode session API exposes no temperature field, so Voyager's | |
| requested temperature is recorded and logged, never forwarded. See | |
| naldo_opencode.TEMPERATURE_NOTE. | |
| Embeddings (SkillManager/QA) are NOT served here; /v1/embeddings proxies to | |
| the existing local 7860 endpoint to preserve upstream behavior. | |
| No OPENCODE_API_KEY is used. No credential file is read or copied. Logs never | |
| include prompts, completions, or credentials. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import threading | |
| import time | |
| import urllib.error | |
| import urllib.request | |
| import uuid | |
| from typing import Any | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.responses import JSONResponse, StreamingResponse | |
| from starlette.concurrency import run_in_threadpool | |
| from naldo_opencode import ( | |
| ALLOWED_MODELS, | |
| BRIDGE_PORT, | |
| FALLBACK_MODELS, | |
| LOCAL_EMBED_BASE, | |
| OPENCODE_BASE, | |
| OPENCODE_PROVIDER, | |
| OPENCODE_SESSION_DIR, | |
| PRIMARY_MODEL, | |
| TEMPERATURE_NOTE, | |
| build_opencode_prompt, | |
| canonical_model_id, | |
| extract_assistant_text, | |
| is_allowed_model, | |
| opencode_message_body, | |
| ) | |
| OPENCODE = OPENCODE_BASE.rstrip("/") | |
| EMBED_BASE = LOCAL_EMBED_BASE.rstrip("/") | |
| PORT = int(os.environ.get("NALDO_BRIDGE_PORT", str(BRIDGE_PORT))) | |
| REQUEST_TIMEOUT = float(os.environ.get("NALDO_BRIDGE_TIMEOUT", "240")) | |
| SESSION_TIMEOUT = float(os.environ.get("NALDO_BRIDGE_SESSION_TIMEOUT", "15")) | |
| EMBED_RETRIES = max(1, int(os.environ.get("NALDO_EMBED_RETRIES", "8"))) | |
| EMBED_BACKOFF = max(0.1, float(os.environ.get("NALDO_EMBED_BACKOFF", "1"))) | |
| EMBED_RETRYABLE = frozenset({429, 500, 502, 503, 504}) | |
| STATE_LOCK = threading.Lock() | |
| state: dict[str, Any] = { | |
| "calls": 0, | |
| "failures": 0, | |
| "fallbacks_used": 0, | |
| "last_model": None, | |
| "last_fallback": None, | |
| "last_elapsed_s": None, | |
| "prompt_tokens": 0, | |
| "completion_tokens": 0, | |
| "last_error": None, | |
| "last_requested_temperature": None, | |
| "last_requested_max_tokens": None, | |
| "effective_temperature": TEMPERATURE_NOTE, | |
| "opencode_base": OPENCODE, | |
| } | |
| def _http_json( | |
| url: str, | |
| method: str = "GET", | |
| body: dict[str, Any] | None = None, | |
| timeout: float = 10.0, | |
| retries: int = 2, | |
| ) -> Any: | |
| last: Exception | None = None | |
| for attempt in range(retries + 1): | |
| try: | |
| data = json.dumps(body).encode("utf-8") if body is not None else None | |
| request = urllib.request.Request(url, data=data, method=method) | |
| if data is not None: | |
| request.add_header("Content-Type", "application/json") | |
| with urllib.request.urlopen(request, timeout=timeout) as response: | |
| raw = response.read().decode("utf-8", errors="replace") | |
| return json.loads(raw) if raw else {} | |
| except Exception as exc: # noqa: BLE001 - mapped to HTTP below | |
| last = exc | |
| if attempt < retries: | |
| time.sleep(min(2.0 * (attempt + 1), 5.0)) | |
| raise RuntimeError(f"{type(last).__name__}: {str(last)[:200]}" if last else "request failed") | |
| def _create_session(model_id: str, timeout: float = SESSION_TIMEOUT) -> str: | |
| model = canonical_model_id(model_id) | |
| if model not in ALLOWED_MODELS: | |
| raise RuntimeError(f"refusing non-OpenCode/free session model: {model_id}") | |
| OPENCODE_SESSION_DIR.mkdir(parents=True, exist_ok=True) | |
| session_body = { | |
| "title": "naldo-voyager", | |
| "model": {"id": model, "providerID": OPENCODE_PROVIDER}, | |
| "location": {"directory": str(OPENCODE_SESSION_DIR)}, | |
| } | |
| payload = _http_json( | |
| OPENCODE + "/session", method="POST", body=session_body, timeout=timeout, retries=0 | |
| ) | |
| session_id = "" | |
| if isinstance(payload, dict): | |
| session_id = str(payload.get("id") or payload.get("sessionID") or payload.get("session_id") or "") | |
| info = payload.get("info") | |
| if not session_id and isinstance(info, dict): | |
| session_id = str(info.get("id") or "") | |
| if not session_id: | |
| raise RuntimeError("OpenCode session creation returned no id") | |
| return session_id | |
| def _send_message(session_id: str, model_id: str, system: str, prompt: str) -> str: | |
| body = opencode_message_body(model_id, system, prompt) | |
| url = OPENCODE + f"/session/{session_id}/message" | |
| try: | |
| # Never retry the same generation POST: a loopback timeout can mean the | |
| # first request is still running, and retrying would duplicate the turn. | |
| result = _http_json(url, method="POST", body=body, timeout=REQUEST_TIMEOUT, retries=0) | |
| finally: | |
| try: | |
| _http_json(OPENCODE + f"/session/{session_id}", method="DELETE", timeout=5.0) | |
| except Exception: | |
| pass | |
| text = extract_assistant_text(result) | |
| if not text: | |
| raise RuntimeError("OpenCode returned no assistant text parts") | |
| return text | |
| def _candidate_models(requested: str) -> list[str]: | |
| ordered: list[str] = [] | |
| bare_requested = canonical_model_id(requested) | |
| if bare_requested in ALLOWED_MODELS: | |
| ordered.append(bare_requested) | |
| for model in FALLBACK_MODELS: | |
| if model not in ordered: | |
| ordered.append(model) | |
| return ordered | |
| def _chat_payload(body: dict[str, Any]) -> dict[str, Any]: | |
| requested_model = str(body.get("model") or PRIMARY_MODEL) | |
| if not is_allowed_model(requested_model): | |
| raise ValueError(f"Unknown model for Naldo OpenCode bridge: {requested_model}") | |
| requested_temp = body.get("temperature") | |
| requested_max_tokens = body.get("max_tokens", body.get("max_completion_tokens")) | |
| with STATE_LOCK: | |
| state["last_requested_temperature"] = requested_temp | |
| state["last_requested_max_tokens"] = requested_max_tokens | |
| state["effective_temperature"] = TEMPERATURE_NOTE | |
| if isinstance(requested_temp, (int, float)) and float(requested_temp) != 0.0: | |
| # Voyager normally sends temperature 0; OpenCode session API has no | |
| # temperature parameter, so record drift without inventing support. | |
| print( | |
| f"[naldo-bridge] requested temperature={requested_temp}; {TEMPERATURE_NOTE}", | |
| flush=True, | |
| ) | |
| # max_tokens is also not supported by the OpenCode session endpoint. Keep | |
| # it only as telemetry: never character-truncate JavaScript/JSON responses. | |
| rendered = build_opencode_prompt(body.get("messages") or []) | |
| last_error = "OpenCode inference failed" | |
| tried: list[str] = [] | |
| for model_id in _candidate_models(requested_model): | |
| tried.append(model_id) | |
| started = time.monotonic() | |
| try: | |
| session_id = _create_session(model_id) | |
| answer = _send_message(session_id, model_id, rendered["system"], rendered["prompt"]) | |
| except Exception as exc: # noqa: BLE001 - retried across free models | |
| last_error = f"{type(exc).__name__}: {str(exc)[:200]}" | |
| continue | |
| elapsed = round(time.monotonic() - started, 2) | |
| with STATE_LOCK: | |
| state["calls"] += 1 | |
| state["last_model"] = model_id | |
| state["last_elapsed_s"] = elapsed | |
| state["last_error"] = None | |
| if model_id != canonical_model_id(requested_model): | |
| state["fallbacks_used"] += 1 | |
| state["last_fallback"] = model_id | |
| else: | |
| state["last_fallback"] = None | |
| return { | |
| "id": "chatcmpl-" + uuid.uuid4().hex[:16], | |
| "object": "chat.completion", | |
| "created": int(time.time()), | |
| "model": canonical_model_id(requested_model), | |
| "choices": [ | |
| {"index": 0, "message": {"role": "assistant", "content": answer}, "finish_reason": "stop"} | |
| ], | |
| "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}, | |
| "backend": "opencode-bridge", | |
| "provider": OPENCODE_PROVIDER, | |
| } | |
| with STATE_LOCK: | |
| state["failures"] += 1 | |
| state["last_error"] = f"tried={tried} last={last_error}" | |
| raise RuntimeError(f"All OpenCode free models failed (tried={tried}): {last_error}") | |
| api = FastAPI(title="Naldo OpenCode Bridge", version="1.0.0") | |
| def health() -> dict[str, Any]: | |
| try: | |
| upstream = _http_json(OPENCODE + "/global/health", timeout=5.0, retries=0) | |
| upstream_ok = bool(isinstance(upstream, dict) and upstream.get("healthy") is True) | |
| except Exception as exc: # noqa: BLE001 - health must not raise | |
| upstream = {"error": f"{type(exc).__name__}"} | |
| upstream_ok = False | |
| with STATE_LOCK: | |
| snapshot = dict(state) | |
| return { | |
| "ready": upstream_ok, | |
| "bridge": "naldo-opencode", | |
| "provider": OPENCODE_PROVIDER, | |
| "primary_model": PRIMARY_MODEL, | |
| "fallback_models": list(FALLBACK_MODELS), | |
| "opencode_base": OPENCODE, | |
| "opencode": {"ok": upstream_ok, "detail": "ok" if upstream_ok else upstream}, | |
| "temperature": TEMPERATURE_NOTE, | |
| **{k: v for k, v in snapshot.items() if k != "opencode_base"}, | |
| } | |
| def models() -> dict[str, Any]: | |
| now = int(time.time()) | |
| return { | |
| "object": "list", | |
| "data": [ | |
| {"id": model, "object": "model", "owned_by": OPENCODE_PROVIDER} | |
| for model in FALLBACK_MODELS | |
| ] | |
| + [ | |
| {"id": f"{OPENCODE_PROVIDER}/{model}", "object": "model", "owned_by": OPENCODE_PROVIDER} | |
| for model in FALLBACK_MODELS | |
| ], | |
| } | |
| async def chat(request: Request) -> Any: | |
| try: | |
| body = await request.json() | |
| except Exception as exc: | |
| raise HTTPException(status_code=400, detail=f"Invalid JSON: {str(exc)[:160]}") from exc | |
| if not isinstance(body.get("messages"), list): | |
| raise HTTPException(status_code=400, detail="messages must be a list") | |
| requested_model = str(body.get("model") or PRIMARY_MODEL) | |
| if not is_allowed_model(requested_model): | |
| raise HTTPException(status_code=404, detail=f"Unknown model: {requested_model}") | |
| try: | |
| payload = await run_in_threadpool(_chat_payload, body) | |
| except ValueError as exc: | |
| raise HTTPException(status_code=404, detail=str(exc)[:200]) from None | |
| except RuntimeError as exc: | |
| raise HTTPException(status_code=503, detail=str(exc)[:800]) from None | |
| if not body.get("stream"): | |
| return JSONResponse(payload) | |
| content = payload["choices"][0]["message"]["content"] | |
| chunk = { | |
| "id": payload["id"], | |
| "object": "chat.completion.chunk", | |
| "created": payload["created"], | |
| "model": payload["model"], | |
| "choices": [{"index": 0, "delta": {"role": "assistant", "content": content}, "finish_reason": "stop"}], | |
| } | |
| async def events(): # pragma: no cover - streaming passthrough | |
| yield "data: " + json.dumps(chunk, ensure_ascii=False) + "\n\n" | |
| yield "data: [DONE]\n\n" | |
| return StreamingResponse(events(), media_type="text/event-stream") | |
| def _forward_embeddings(raw: bytes) -> dict[str, Any]: | |
| last_error: Exception | None = None | |
| for attempt in range(EMBED_RETRIES): | |
| forward = urllib.request.Request( | |
| EMBED_BASE.rstrip("/") + "/embeddings", data=raw, method="POST" | |
| ) | |
| forward.add_header("Content-Type", "application/json") | |
| api_key = os.environ.get("API_KEY", "").strip() | |
| if api_key: | |
| forward.add_header("Authorization", f"Bearer {api_key}") | |
| try: | |
| with urllib.request.urlopen(forward, timeout=120) as response: | |
| return json.loads(response.read().decode("utf-8")) | |
| except urllib.error.HTTPError as exc: | |
| last_error = exc | |
| if exc.code not in EMBED_RETRYABLE: | |
| raise | |
| except (urllib.error.URLError, TimeoutError, OSError) as exc: | |
| last_error = exc | |
| if attempt + 1 < EMBED_RETRIES: | |
| time.sleep(min(EMBED_BACKOFF * (2 ** attempt), 8.0)) | |
| assert last_error is not None | |
| raise last_error | |
| async def embeddings(request: Request) -> Any: | |
| """Proxy embeddings with bounded retry so transient boot 503s do not kill Voyager.""" | |
| raw = await request.body() | |
| try: | |
| payload = await run_in_threadpool(_forward_embeddings, raw) | |
| return JSONResponse(payload) | |
| except urllib.error.HTTPError as exc: | |
| status = exc.code if 400 <= exc.code < 500 and exc.code not in EMBED_RETRYABLE else 503 | |
| raise HTTPException(status_code=status, detail=f"embedding backend unavailable: HTTP {exc.code}") from None | |
| except Exception as exc: # noqa: BLE001 - hide upstream internals | |
| raise HTTPException(status_code=503, detail=f"embedding backend unavailable: {type(exc).__name__}") from None | |
| if __name__ == "__main__": | |
| import uvicorn | |
| # Loopback only: never expose OpenCode or this bridge externally. | |
| uvicorn.run(api, host="127.0.0.1", port=PORT, log_level="warning") | |