"""ReinOpenWeights — glas-harness adapter around the rein-openweights CLI. Same shape as reins/rein_aharness.py: rein-openweights's `run` command performs an entire bounded agentic session (credential acquisition, OpenRouter tool-calling loop, commit verification) as one opaque unit — no per-tool-call hooks exposed yet. dispatch_tool collapses the whole run into a single call. """ from __future__ import annotations import shutil import subprocess from typing import Any from glas_harness.contract import ( ExecutionSummary, HarnessProfile, Rein, SandboxHandle, ToolCall, ToolResult, ) from glas_harness.reins._shared import git_head, parse_json_object, write_task_file class ReinOpenWeightsNotInstalled(RuntimeError): pass class ReinOpenWeights(Rein): def __init__( self, cli_bin: str = "rein-openweights", model: str | None = None, max_turns: int | None = None, budget_tokens: int | None = None, tool_profile: str | None = None, ) -> None: self.cli_bin = cli_bin self.model = model self.max_turns = max_turns self.budget_tokens = budget_tokens self.tool_profile = tool_profile self._last_result: dict[str, Any] = {} def _bin(self) -> str: resolved = shutil.which(self.cli_bin) if not resolved: raise ReinOpenWeightsNotInstalled( f"'{self.cli_bin}' not found on PATH — install rein-openweights first" ) return resolved def start_session( self, profile: HarnessProfile, inputs: dict[str, str], sandbox: SandboxHandle ) -> dict[str, str]: target_repo = ( inputs.get("target_repo") or sandbox.reachability.get("workspace_dir") or sandbox.reachability.get("remote_dir") ) if not target_repo: raise ValueError("no target_repo resolvable from inputs or sandbox reachability") task_file = inputs.get("task_file") or write_task_file( inputs["title"], inputs["description"], target_repo, timeout_seconds=profile.limits.timeout_seconds or 600, ) return { "sandbox_id": sandbox.sandbox_id, "task_file": task_file, "target_repo": target_repo, "head_before": git_head(target_repo), } def dispatch_tool(self, session: dict[str, str], tool_call: ToolCall) -> ToolResult: argv = [self._bin(), "run", "--task-file", session["task_file"], "--no-hub"] if self.model: argv += ["--model", self.model] if self.max_turns: argv += ["--max-turns", str(self.max_turns)] if self.budget_tokens: argv += ["--budget-tokens", str(self.budget_tokens)] if self.tool_profile: argv += ["--tool-profile", self.tool_profile] proc = subprocess.run(argv, capture_output=True, text=True) ok = proc.returncode == 0 self._last_result = parse_json_object(proc.stdout) return ToolResult( ok=ok, output=proc.stdout, error=None if ok else (proc.stderr or self._last_result.get("reason")), events_completeness="unavailable", tokens_spent=self._last_result.get("tokens_spent"), duration_s=self._last_result.get("execution_time_s"), resolved_model=self._last_result.get("model") or self.model, metadata={ "turns": self._last_result.get("turns"), "tool_profile": self._last_result.get("tool_profile") or self.tool_profile, }, ) def end_session(self, session: dict[str, str]) -> ExecutionSummary: head_after = git_head(session["target_repo"]) committed = bool(head_after) and head_after != session.get("head_before") reported_ok = bool(self._last_result.get("ok", committed)) reason = self._last_result.get("reason") or None outcome = "succeeded" if committed and reported_ok else ( "refused" if reason == "no OpenRouter credential resolved" else "failed" ) return ExecutionSummary( commit_sha=head_after or None, committed=committed, outcome=outcome, reason=reason, tokens_spent=self._last_result.get("tokens_spent"), duration_s=self._last_result.get("execution_time_s"), resolved_model=self._last_result.get("model") or self.model, artifacts=[head_after] if committed and head_after else [], metadata={ "turns": self._last_result.get("turns"), "tool_profile": self._last_result.get("tool_profile") or self.tool_profile, }, )