Udud / opencode_runtime.py
Erinaldordso1996's picture
Authorize web MCP from existing Space Secrets
56bf429 verified
Raw History Blame Contribute Delete
23.1 kB
"""Official OpenCode UI/client; inference remains owned by llama-server."""
from __future__ import annotations
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tarfile
import time
from pathlib import Path
import httpx
from opencode_history import HistoryStore
from opencode_mcp_auth import ensure_pc_mcp_auth
OPENCODE_VERSION = "1.18.35"
OPENCODE_ARCHIVE_URL = (
"https://github.com/anomalyco/opencode/releases/download/"
f"v{OPENCODE_VERSION}/opencode-linux-x64.tar.gz"
)
OPENCODE_ARCHIVE_SHA256 = "c8f888b451f5494a18f858fffb0e0b68f4e4baa9c241761c5f206884f0fa640d"
PROVIDER_ID = "llamacpp"
ZEN_FREE_PROVIDER = "opencode"
# Free-only mode (SKIP_LLAMA): OpenCode's own Zen models, key from the
# OPENCODE_ZEN_API_KEY space secret (alias of OPENCODE_API_KEY). Curated free
# IDs below were live on the Zen catalog at the Oct 10 audit; the picker
# resyncs when the catalog moves.
# Proven live Oct 14 with real tool calls through this exact stack; big-pickle
# role-plays tool calls as text instead of invoking them, so it stays out.
ZEN_DEFAULT_MODEL = "nemotron-3-ultra-free"
ZEN_SMALL_MODEL = "nemotron-3-ultra-free"
def ensure_opencode(root: Path) -> Path:
"""Install only the pinned official client, checking its release digest."""
binary = root / "bin" / "opencode"
manifest = root / "verified-binary.json"
if binary.exists():
try:
saved = json.loads(manifest.read_text(encoding="utf-8"))
with binary.open("rb") as cached:
actual = hashlib.file_digest(cached, "sha256").hexdigest()
except (OSError, ValueError) as exc:
raise RuntimeError("Cached OpenCode has no readable integrity record") from exc
if saved.get("archive_sha256") != OPENCODE_ARCHIVE_SHA256 or saved.get("binary_sha256") != actual:
raise RuntimeError("Cached OpenCode integrity mismatch; refusing to execute")
if not binary.exists():
binary.parent.mkdir(parents=True, exist_ok=True)
archive = root / "opencode.tar.gz.part"
pending = binary.with_suffix(".part")
digest = hashlib.sha256()
print(f"[opencode] downloading official client v{OPENCODE_VERSION}", flush=True)
try:
with httpx.stream("GET", OPENCODE_ARCHIVE_URL, follow_redirects=True, timeout=90) as response:
response.raise_for_status()
size = 0
with archive.open("wb") as output:
for chunk in response.iter_bytes():
size += len(chunk)
if size > 150_000_000:
raise RuntimeError("OpenCode archive exceeds the expected size limit")
digest.update(chunk)
output.write(chunk)
if digest.hexdigest() != OPENCODE_ARCHIVE_SHA256:
raise RuntimeError("OpenCode release SHA-256 mismatch; refusing to execute")
with tarfile.open(archive, "r:gz") as bundle:
members = [m for m in bundle.getmembers() if m.name in {"opencode", "./opencode"}]
if len(members) != 1 or not members[0].isfile():
raise RuntimeError("Official OpenCode archive has an unexpected layout")
source = bundle.extractfile(members[0])
if source is None:
raise RuntimeError("OpenCode executable missing from archive")
with source, pending.open("wb") as output:
shutil.copyfileobj(source, output)
pending.chmod(0o755)
pending.replace(binary)
with binary.open("rb") as installed:
binary_hash = hashlib.file_digest(installed, "sha256").hexdigest()
manifest.write_text(json.dumps({"archive_sha256": OPENCODE_ARCHIVE_SHA256,
"binary_sha256": binary_hash}), encoding="utf-8")
finally:
archive.unlink(missing_ok=True)
pending.unlink(missing_ok=True)
version = subprocess.check_output([str(binary), "--version"], text=True, timeout=30).strip()
if version != OPENCODE_VERSION:
raise RuntimeError(f"Unexpected OpenCode version: {version!r}")
print(f"[opencode] official client version={version} archive_sha256={OPENCODE_ARCHIVE_SHA256}", flush=True)
return binary
def make_config(*, model_id: str | None, context: int, api_port: int, ui_port: int, bridge: Path,
mcp_url: str, token_url: str, free_only: bool = False) -> dict:
if free_only:
enabled_providers = [ZEN_FREE_PROVIDER]
selected = f"{ZEN_FREE_PROVIDER}/{ZEN_DEFAULT_MODEL}"
small_selected = f"{ZEN_FREE_PROVIDER}/{ZEN_SMALL_MODEL}"
providers: dict = {
ZEN_FREE_PROVIDER: {},
}
else:
if not model_id or context < 8192:
raise ValueError("Existing model ID and context are required")
selected = f"{PROVIDER_ID}/{model_id}"
enabled_providers = [PROVIDER_ID, ZEN_FREE_PROVIDER]
providers = {
PROVIDER_ID: {
"npm": "@ai-sdk/openai-compatible",
"name": "llama.cpp CPU-only",
"whitelist": [model_id],
"options": {"baseURL": f"http://127.0.0.1:{api_port}/v1", "timeout": 900000},
"models": {
model_id: {
"name": "Naldo Coder (CPU)",
"tool_call": True,
"reasoning": True,
"limit": {"context": context, "output": 32768},
}
},
},
ZEN_FREE_PROVIDER: {},
}
return {
"$schema": "https://opencode.ai/config.json",
"autoupdate": False,
"share": "disabled",
"enabled_providers": enabled_providers,
"model": selected,
"small_model": small_selected if free_only else selected,
"default_agent": "1agent",
"server": {"hostname": "127.0.0.1", "port": ui_port},
"provider": providers,
"mcp": {
"naldo_pc": {
"type": "remote",
"url": "https://naldo-mcp-render.onrender.com/mcp",
"enabled": True,
"timeout": 180000,
}
},
"lsp": False,
"formatter": False,
"permission": {
"*": "deny",
"external_directory": "deny",
"edit": "allow",
"glob": "allow",
"grep": "allow",
"list": "allow",
"invalid": "allow",
"read": "allow",
"skill": {"*": "ask", "1agent-policy": "allow"},
"bash": "allow",
"naldo_pc_*": "allow",
"websearch": "allow",
"webfetch": "allow",
"todowrite": "allow",
"question": "allow",
},
"agent": {
"1agent": {
"description": "Agente personalizado 1agent.md para testes privados",
"mode": "primary",
"prompt": "{file:./prompts/1agent.md}",
"temperature": 0.0,
},
"build": {
"prompt": (
"Responda em portugues do Brasil. Voce e Naldo Agent. "
"O computador Windows do usuario e acessado pelas ferramentas naldo_pc. "
"O ambiente local deste servidor e o Space Linux, nao o PC do usuario. "
"Use naldo_pc_start_process com PowerShell puro; ele ja usa powershell.exe. "
"Quando receber um PID, acompanhe esse mesmo PID com read_process_output; "
"nao repita start_process para consultar o mesmo comando. "
"Use websearch para pesquisar e webfetch para ler URLs verificadas. "
"Nao invente resultados nem exponha segredos. "
"O modelo padrao e o llama.cpp CPU-only; alternativas Zen Free sao externas, nunca use a GPU do Space. "
"Quando a ferramenta responder ao pedido, conclua sem repetir chamadas."
)
}
},
}
def prepare_config(*, root: Path, expected_model: str, context: int, api_port: int,
ui_port: int, bridge: Path, mcp_url: str, token_url: str,
validate: bool = True, free_only: bool = False) -> tuple[Path, dict]:
model_id: str | None = None
if validate:
response = httpx.get(f"http://127.0.0.1:{api_port}/v1/models", timeout=10)
response.raise_for_status()
ids = [m.get("id") for m in response.json().get("data", [])]
if ids != [expected_model]:
raise RuntimeError(f"Expected exactly the existing model {expected_model!r}; got {ids!r}")
model_id = ids[0]
else:
print("[opencode] CONFIG=SKIP local model validation (SKIP_LLAMA free-only)", flush=True)
config = make_config(model_id=model_id, context=context, api_port=api_port, ui_port=ui_port,
bridge=bridge, mcp_url=mcp_url, token_url=token_url, free_only=free_only)
root.mkdir(parents=True, exist_ok=True)
source_prompt = Path(__file__).resolve().parent / "agent_prompts" / "1agent.md"
source_data = source_prompt.read_bytes()
if hashlib.sha256(source_data).hexdigest() != "2c28b84910f9f9262460655c13347fb578d32cd2e2fdf9e65de971805de7ce65":
raise RuntimeError("Original 1agent.md integrity check failed")
prompt_dir = root / "prompts"
prompt_dir.mkdir(parents=True, exist_ok=True)
destination = prompt_dir / "1agent.md"
if destination.exists() and destination.read_bytes() != source_data:
raise RuntimeError("Unexpected existing OpenCode agent prompt")
if not destination.exists():
destination.write_bytes(source_data)
# OPENCODE_CONFIG_CONTENT is parsed independently of OPENCODE_CONFIG:
# relative {file:...} references resolve against request-specific directories
# (for example /root/.local), not the source configuration file.
# Absolute file reference keeps the original agent prompt unmodified.
config["agent"]["1agent"]["prompt"] = "{file:" + str(destination.resolve()) + "}"
config["instructions"] = [str(destination.resolve())]
# V1 global AGENTS.md and an optional policy skill supplement instructions.
# The canonical source remains the unchanged, SHA-256-verified 1agent.md.
global_config = root / "config" / "opencode"
global_config.mkdir(parents=True, exist_ok=True)
global_rules = global_config / "AGENTS.md"
global_rules_text = (
"# Global OpenCode instructions\n"
"The canonical project policy is loaded directly through the global "
"opencode.json instructions setting from the integrity-checked "
"1agent.md file. Apply those project instructions in every session "
"and when changing models or agents. Keep the original file intact "
"and respect provider rules and existing tool permissions.\n"
)
if global_rules.exists() and global_rules.read_text(encoding="utf-8") != global_rules_text:
raise RuntimeError("Unexpected global AGENTS.md content")
global_rules.write_text(global_rules_text, encoding="utf-8")
policy_skill = global_config / "skills" / "1agent-policy" / "SKILL.md"
policy_skill.parent.mkdir(parents=True, exist_ok=True)
skill_text = (
"---\n"
"name: 1agent-policy\n"
"description: Verify and apply the canonical OpenCode 1agent.md project policy across agent and model workflows.\n"
"---\n"
"The original 1agent.md is already loaded via global instructions. "
"Apply it to the current engineering task, without altering the original. "
"Verify configuration and relevant logs; do not claim model obedience "
"without a real provider-backed test.\n"
)
if policy_skill.exists() and policy_skill.read_text(encoding="utf-8") != skill_text:
raise RuntimeError("Unexpected policy SKILL.md content")
policy_skill.write_text(skill_text, encoding="utf-8")
target = root / "opencode.json"
target.write_text(json.dumps(config, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
target.chmod(0o600)
print(f"[opencode] provider={config['provider'] and next(iter(config['provider']))} "
f"model={config['model']} free_only={free_only}", flush=True)
return target, config
def refresh_zen_models_if_idle(*, root: Path, ui_port: int, auth) -> None:
"""Rebuild the OpenCode model picker only when free Zen catalog IDs change."""
cache_file = root / "cache" / "opencode" / "models.json"
if not cache_file.is_file():
return
try:
catalog = json.loads(cache_file.read_text(encoding="utf-8"))
zen = catalog.get(ZEN_FREE_PROVIDER, {}).get("models", {})
if not isinstance(zen, dict):
return
free_ids = {
mid for mid, model in zen.items()
if isinstance(model, dict)
and model.get("cost", {}).get("input") == 0
and model.get("cost", {}).get("output") == 0
and model.get("status") != "deprecated"
}
if not free_ids:
return
base = f"http://127.0.0.1:{ui_port}"
current = httpx.get(f"{base}/provider", auth=auth, timeout=10)
current.raise_for_status()
providers = current.json().get("all", [])
existing = next((set(p.get("models", {})) for p in providers
if p.get("id") == ZEN_FREE_PROVIDER), None)
if existing == free_ids:
return
if existing is None:
print("[opencode] ZEN_CATALOG_SYNC=SKIPPED provider_unavailable", flush=True)
return
status = httpx.get(f"{base}/session/status", auth=auth, timeout=10)
status.raise_for_status()
if status.json():
print("[opencode] ZEN_CATALOG_SYNC=DEFERRED active_session", flush=True)
return
result = httpx.post(f"{base}/instance/dispose", auth=auth, timeout=15)
result.raise_for_status()
verify = httpx.get(f"{base}/provider", auth=auth, timeout=15)
verify.raise_for_status()
updated = next((set(p.get("models", {})) for p in verify.json().get("all", [])
if p.get("id") == ZEN_FREE_PROVIDER), set())
if updated == free_ids:
print(f"[opencode] ZEN_CATALOG_SYNC=PASS old={len(existing)} new={len(updated)}", flush=True)
else:
print(f"[opencode] ZEN_CATALOG_SYNC=PENDING cached={len(updated)} catalog={len(free_ids)}", flush=True)
except (OSError, ValueError, TypeError, KeyError, httpx.HTTPError) as error:
print(f"[opencode] ZEN_CATALOG_SYNC=ERROR type={type(error).__name__}", flush=True)
def stop_process(process: subprocess.Popen) -> None:
if process.poll() is not None:
return
process.terminate()
try:
process.wait(timeout=15)
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
def run_opencode_ui(*, binary: Path, model_process: subprocess.Popen | None, root: Path,
expected_model: str, context: int, api_port: int, ui_port: int,
bridge: Path, mcp_url: str, token_url: str,
validate: bool = True, free_only: bool = False) -> None:
config_path, config = prepare_config(root=root, expected_model=expected_model, context=context,
api_port=api_port, ui_port=ui_port, bridge=bridge, mcp_url=mcp_url, token_url=token_url,
validate=validate, free_only=free_only)
private_mount = Path("/persistent")
if not private_mount.is_dir():
# Never silently create ephemeral projects that vanish at restart.
raise RuntimeError("Private project bucket unavailable; refusing temporary workspace")
persistence = HistoryStore(local_data=root / "data", mounted=private_mount)
workspace = persistence.prepare()
git_marker = workspace / ".git" / "HEAD"
if not git_marker.is_file():
raise RuntimeError("Persistent Git workspace missing; refusing global temporary project")
verify = subprocess.run(["git", "-c", f"safe.directory={workspace}", "rev-parse", "--verify", "HEAD"],
cwd=workspace, capture_output=True, timeout=15, text=True)
if verify.returncode != 0 or len(verify.stdout.strip()) != 40:
print(f"[opencode] PROJECT_VCS_CHECK_FAIL rc={verify.returncode} "
f"stdout_chars={len(verify.stdout.strip())} stderr={verify.stderr.strip()[:260]!r} "
f"git_marker_bytes={git_marker.stat().st_size}", flush=True)
raise RuntimeError("Persistent project Git revision invalid; refusing global fallback")
print("[opencode] PROJECT_PERSISTENCE=PASS PRIVATE_BUCKET=ON", flush=True)
print("[opencode] PROJECT_VCS=PASS", flush=True)
# OPENCODE_CONFIG_CONTENT resolves {file:./...} relative to the process CWD.
source_prompt = root / "prompts" / "1agent.md"
target_prompt = workspace / "prompts" / "1agent.md"
content = source_prompt.read_bytes()
if hashlib.sha256(content).hexdigest() != "2c28b84910f9f9262460655c13347fb578d32cd2e2fdf9e65de971805de7ce65":
raise RuntimeError("OpenCode agent prompt does not match original")
target_prompt.parent.mkdir(parents=True, exist_ok=True)
if target_prompt.exists() and target_prompt.read_bytes() != content:
raise RuntimeError("Unexpected project 1agent.md content")
if not target_prompt.exists():
target_prompt.write_bytes(content)
env = os.environ.copy()
# Zen reads OPENCODE_API_KEY, accepting OPENCODE_ZEN_API_KEY as an alias.
# The key lives only in the Space secret and the process env, never in files.
zen_key = env.get("OPENCODE_ZEN_API_KEY") or env.get("OPENCODE_API_KEY")
if zen_key:
env["OPENCODE_API_KEY"] = zen_key
env["OPENCODE_ZEN_API_KEY"] = zen_key
print("[opencode] ZEN_KEY=PRESENT (from secret, not logged)", flush=True)
elif free_only:
print("[opencode] ZEN_KEY=MISSING add OPENCODE_ZEN_API_KEY as a Space secret", flush=True)
# Enable OpenCode Basic Auth from Hugging Face Space Secrets.
if not env.get("OPENCODE_SERVER_USERNAME") or not env.get("OPENCODE_SERVER_PASSWORD"):
raise RuntimeError("Missing OpenCode login secrets")
env.pop("OPENCODE_DISABLE_MODELS_FETCH", None)
env.update({
"OPENCODE_CONFIG": str(config_path),
"OPENCODE_CONFIG_CONTENT": json.dumps(config),
"OPENCODE_DISABLE_AUTOUPDATE": "1",
"OPENCODE_DISABLE_PROJECT_CONFIG": "1",
"OPENCODE_ENABLE_EXA": "1",
"XDG_CONFIG_HOME": str(root / "config"),
"XDG_DATA_HOME": str(root / "data"),
"XDG_CACHE_HOME": str(root / "cache"),
# Protected, narrow Git trust: only the user's private mounted workspace.
"GIT_CONFIG_COUNT": "2",
"GIT_CONFIG_KEY_0": "safe.directory",
"GIT_CONFIG_VALUE_0": str(workspace),
"GIT_CONFIG_KEY_1": "safe.directory",
"GIT_CONFIG_VALUE_1": str(workspace) + "/*",
"PYTHONIOENCODING": "utf-8",
})
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
try:
probe.bind(("127.0.0.1", ui_port))
except OSError as error:
raise RuntimeError(f"OpenCode port {ui_port} not available: {type(error).__name__} {error.errno}") from error
print(f"[opencode] PORT_BIND_PROBE=PASS {ui_port}", flush=True)
log_path = root / "opencode-serve.log"
log_handle = log_path.open("ab", buffering=0)
log_handle.write(f"\n=== boot {time.time()} free_only={free_only} ===\n".encode())
ui = subprocess.Popen([str(binary), "--print-logs", "--log-level", "INFO",
"web", "--hostname", "127.0.0.1", "--port", str(ui_port)],
cwd=workspace, env=env, stdout=log_handle, stderr=subprocess.STDOUT)
print(f"[opencode] LOG={log_path}", flush=True)
deadline = time.monotonic() + 120
ready = False
last_snapshot = time.monotonic()
last_catalog_sync = time.monotonic() - 120
try:
while True:
if model_process is not None and model_process.poll() is not None:
raise RuntimeError(f"llama-server exited: {model_process.returncode}")
if ui.poll() is not None:
raise RuntimeError(f"OpenCode exited: {ui.returncode}")
if not ready:
try:
password = env.get("OPENCODE_SERVER_PASSWORD")
auth = (env.get("OPENCODE_SERVER_USERNAME", "opencode"), password) if password else None
response = httpx.get(f"http://127.0.0.1:{ui_port}/global/health", timeout=2, auth=auth)
ready = response.status_code == 200 and response.json().get("healthy") is True
except (httpx.HTTPError, ValueError):
pass
if ready:
try:
mcp_ready = ensure_pc_mcp_auth(
base_url=f"http://127.0.0.1:{ui_port}",
data_home=root / "data", env=env)
print(f"[opencode] MCP_AUTH={'CONNECTED' if mcp_ready else 'NEEDS_AUTH'} "
"source=SpaceSecrets", flush=True)
except (httpx.HTTPError, OSError, ValueError, RuntimeError) as error:
print(f"[opencode] MCP_AUTH=ERROR type={type(error).__name__}", flush=True)
print(f"[opencode] official Web UI ready on 0.0.0.0:{ui_port}; MCP=naldo_pc "
"websearch=enabled webfetch=enabled", flush=True)
elif time.monotonic() >= deadline:
raise TimeoutError("OpenCode Web did not become healthy within 120 seconds")
if ready and time.monotonic() - last_catalog_sync >= 120:
password = env.get("OPENCODE_SERVER_PASSWORD")
auth = (env.get("OPENCODE_SERVER_USERNAME", "opencode"), password) if password else None
refresh_zen_models_if_idle(root=root, ui_port=ui_port, auth=auth)
last_catalog_sync = time.monotonic()
if ready and persistence and time.monotonic() - last_snapshot >= 90:
try:
persistence.snapshot()
except (OSError, RuntimeError, ValueError) as error:
print(f"[opencode] SESSION_SNAPSHOT=ERROR type={type(error).__name__}", flush=True)
last_snapshot = time.monotonic()
time.sleep(1)
finally:
stop_process(ui)
try:
log_handle.close()
except OSError:
pass
if persistence:
try:
persistence.snapshot()
except (OSError, RuntimeError, ValueError) as error:
print(f"[opencode] FINAL_SESSION_SNAPSHOT=ERROR type={type(error).__name__}", flush=True)