Base class for agents that run via OpenCode CLI.
| 87 | # Meta Model API (api.meta.ai) — custom provider declared in |
| 88 | # muse_adapter; the key is referenced as {env:META_MODEL_API_KEY} in opencode.json. |
| 89 | "meta": "META_MODEL_API_KEY", |
| 90 | # z.ai GLM Coding Plan (api.z.ai/api/coding/paas/v4) — native models.dev |
| 91 | # provider used by glm53_adapter. |
| 92 | "zai-coding-plan": "ZHIPU_API_KEY", |
| 93 | } |
| 94 | |
| 95 | |
| 96 | class OpenCodeAdapter(BaseInstalledAgent): |
| 97 | """ |
| 98 | Base class for agents that run via OpenCode CLI. |
| 99 | """ |
| 100 | |
| 101 | _default_model: str = "" |
| 102 | _log_prefix: str = "opencode" |
| 103 | _log_file: str = "opencode.txt" |
| 104 | _model_options: dict = {} # extra options merged into the model config |
| 105 | |
| 106 | def __init__(self, run_timeout_sec: int | None = None, *args, **kwargs): |
| 107 | super().__init__(*args, **kwargs) |
| 108 | self._run_timeout_sec = int(run_timeout_sec) if run_timeout_sec is not None else None |
| 109 | |
| 110 | @property |
| 111 | def _install_agent_template_path(self) -> Path: |
| 112 | return Path(__file__).parent / "install-opencode.sh.j2" |
| 113 | |
| 114 | @staticmethod |
| 115 | def name() -> str: |
| 116 | return "opencode-adapter" |
| 117 | |
| 118 | async def install(self, environment: BaseEnvironment) -> None: |
| 119 | # OpenCode is pre-installed in the Docker image via NVM. |
| 120 | # Verify it's accessible; if not, install it. |
| 121 | result = await environment.exec( |
| 122 | command='export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; command -v opencode', |
| 123 | ) |
| 124 | if result.return_code != 0: |
| 125 | await self.exec_as_agent( |
| 126 | environment, |
| 127 | command=( |
| 128 | "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash && " |
| 129 | 'export NVM_DIR="$HOME/.nvm" && ' |
| 130 | '. "$NVM_DIR/nvm.sh" && ' |
| 131 | "nvm install 22 && npm i -g opencode-ai@latest && " |
| 132 | "opencode --version" |
| 133 | ), |
| 134 | ) |
| 135 | |
| 136 | def populate_context_post_run(self, context: AgentContext) -> None: |
| 137 | """Parse OpenCode JSONL log → ATIF trajectory.json + token counts.""" |
| 138 | log_path = self.logs_dir / self._log_file |
| 139 | if not log_path.exists(): |
| 140 | logger.warning("OpenCode log not found: %s", log_path) |
| 141 | return |
| 142 | |
| 143 | try: |
| 144 | trajectory = _parse_opencode_log( |
| 145 | log_path, |
| 146 | model_name=self.model_name or self._default_model, |
nothing calls this directly
no outgoing calls
no test coverage detected