"""Isolated opencode process runtime.""" import os import subprocess _ENV_ALLOW = frozenset({ "PATH", "LANG", "LANGUAGE", "LC_ALL", "LC_CTYPE", "TZ", "TERM", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS", "NO_PROXY", "no_proxy", }) def shared_home(work_root): home = os.path.join(work_root, ".opencode-home") os.makedirs(home, exist_ok=True) return home def ensure_global_config(home, factory_dir, install_config): dst_dir = os.path.join(home, ".config", "opencode") os.makedirs(dst_dir, exist_ok=True) dst = os.path.join(dst_dir, "opencode.json") src = os.path.join(factory_dir, "opencode.json") if not os.path.isfile(src): return if not os.path.isfile(dst) or os.path.getmtime(src) > os.path.getmtime(dst): install_config(src, dst) def build_env(home, rtk_dir, source_env=None): source = os.environ if source_env is None else source_env env = {key: value for key, value in source.items() if key in _ENV_ALLOW} env["HOME"] = home path = env.get("PATH", "/usr/local/bin:/usr/bin:/bin") env["PATH"] = (rtk_dir + os.pathsep + path) if rtk_dir else path env["OPENCODE_EXPERIMENTAL_LSP_TOOL"] = source.get( "OPENCODE_EXPERIMENTAL_LSP_TOOL", "true" ) return env def warm_opencode( home, model, *, opencode_bin, ensure_config, build_environment, runner=subprocess.run, ): marker = os.path.join(home, ".pragent.warmed") if os.path.exists(marker): return ensure_config(home) env = build_environment(home) try: runner( [opencode_bin, "run", "--pure", "--model", model, "ok"], cwd=home, env=env, capture_output=True, text=True, stdin=subprocess.DEVNULL, timeout=240, ) except (subprocess.TimeoutExpired, Exception): pass try: open(marker, "w").close() except OSError: pass def run_opencode( workdir, model, *, opencode_bin, shared_home_fn, warm_fn, build_environment, parse_events, prompt, timeout, runner=subprocess.run, ): home = shared_home_fn() warm_fn(home, model) env = build_environment(home) cmd = [ opencode_bin, "run", "--pure", "--format", "json", "--agent", "pragent", "--dir", workdir, "--model", model, prompt, ] last_err = "" for _ in range(2): try: proc = runner( cmd, cwd=workdir, env=env, capture_output=True, text=True, stdin=subprocess.DEVNULL, timeout=timeout, ) except subprocess.TimeoutExpired as exc: last_err = f"opencode timed out after {exc.timeout}s" continue text, usage = parse_events(proc.stdout or "") if text.strip(): return text, usage last_err = ( f"opencode empty text (rc={proc.returncode}); " f"stderr: {(proc.stderr or '')[-1500:]}" ) raise RuntimeError(last_err or "opencode produced no output")