from __future__ import annotations import json import os import urllib.error import urllib.request import uuid from dataclasses import dataclass @dataclass class CodexResult: final_response: str finish_reason: str = "completed" class CodexHarness: """Small non-interactive adapter for OpenCode Go's Responses API.""" def __init__(self, model=None, api_key=None, base_url=None, session_root=None, system_prompt=None, **kwargs): self.model = model or os.environ.get("OPENCODE_GO_MODEL", "gpt-5.6-luna") self.api_key = ( api_key or os.environ.get("OPENCODE_GO_API_KEY") or os.environ.get("OPENAI_API_KEY") or "" ).strip() self.base_url = ( base_url or os.environ.get("OPENCODE_GO_BASE_URL") or os.environ.get("OPENAI_BASE_URL") or "https://opencode.ai/zen/go/v1" ).rstrip("/") self.system_prompt = system_prompt or "" @staticmethod def _text(payload: dict) -> str: text = str(payload.get("output_text") or "").strip() if text: return text parts = [] for item in payload.get("output") or []: for content in item.get("content") or []: if content.get("type") in {"output_text", "text"}: value = content.get("text") if isinstance(value, str): parts.append(value) elif isinstance(value, dict) and isinstance(value.get("value"), str): parts.append(value["value"]) return "".join(parts).strip() def run(self, prompt, session_id=None): if not self.api_key: raise RuntimeError("OPENCODE_GO_API_KEY is not configured") full_prompt = (self.system_prompt + "\n\n" + prompt).strip() body = json.dumps({"model": self.model, "input": full_prompt}, ensure_ascii=False).encode("utf-8") request = urllib.request.Request( self.base_url + "/responses", data=body, method="POST", headers={ "Authorization": "Bearer " + self.api_key, "Content-Type": "application/json", "Accept": "application/json", "User-Agent": "Global-Marine-Foundation/4.4", "x-opencode-session": str(session_id or uuid.uuid4()), }, ) try: with urllib.request.urlopen(request, timeout=600) as response: payload = json.loads(response.read().decode("utf-8")) except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace")[:1000] raise RuntimeError(f"OpenCode Go API {exc.code}: {detail}") from exc except urllib.error.URLError as exc: raise RuntimeError(f"OpenCode Go connection failed: {exc.reason}") from exc answer = self._text(payload) if not answer: raise RuntimeError("OpenCode Go returned no text: " + json.dumps(payload, ensure_ascii=False)[:800]) return CodexResult(final_response=answer)