Spaces:
Paused
Paused
Download opencode_runtime.py from Erinaldordso1996/Udud: direct link, hf CLI and curl.
- Browser
- Download file 23.1 kB
-
https://huggingface.co/spaces/Erinaldordso1996/Udud/resolve/main/opencode_runtime.py
- Command line
-
hf download hf://spaces/Erinaldordso1996/Udud/opencode_runtime.py
-
curl -L -o opencode_runtime.py https://huggingface.co/spaces/Erinaldordso1996/Udud/resolve/main/opencode_runtime.py
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) | |