diff --git a/sdk/python/agentfield/harness/_runner.py b/sdk/python/agentfield/harness/_runner.py index 5321c985..a8b010b2 100644 --- a/sdk/python/agentfield/harness/_runner.py +++ b/sdk/python/agentfield/harness/_runner.py @@ -70,7 +70,6 @@ def _resolve_options( "codex_bin", "gemini_bin", "opencode_bin", - "opencode_server", "schema_max_retries", ]: val = getattr(config, field_name, None) @@ -146,17 +145,7 @@ async def run( resolved_cwd = str(options.get("cwd", ".")) provider_instance = self._build_provider(str(resolved_provider), options) - # When project_dir is set (opencode provider), place the output file - # inside project_dir so the coding agent's Write tool can reach it. - # Use a unique subdir to avoid collisions from parallel calls. - project_dir = options.get("project_dir") output_dir = resolved_cwd - _temp_output_dir: Optional[str] = None - if isinstance(project_dir, str) and project_dir: - import tempfile as _tempfile - - _temp_output_dir = _tempfile.mkdtemp(prefix=".secaf-out-", dir=project_dir) - output_dir = _temp_output_dir effective_prompt = prompt if schema is not None: @@ -195,10 +184,6 @@ async def run( finally: if schema is not None: cleanup_temp_files(output_dir) - if _temp_output_dir: - import shutil as _shutil - - _shutil.rmtree(_temp_output_dir, ignore_errors=True) def _build_provider( self, provider_name: str, options: Dict[str, Any] diff --git a/sdk/python/agentfield/harness/providers/_factory.py b/sdk/python/agentfield/harness/providers/_factory.py index e3d3f259..a7d36460 100644 --- a/sdk/python/agentfield/harness/providers/_factory.py +++ b/sdk/python/agentfield/harness/providers/_factory.py @@ -33,6 +33,5 @@ def build_provider(config: "HarnessConfig") -> "HarnessProvider": return OpenCodeProvider( bin_path=getattr(config, "opencode_bin", "opencode"), - server_url=getattr(config, "opencode_server", None), ) raise NotImplementedError(f"Provider {provider_name!r} is not yet implemented.") diff --git a/sdk/python/agentfield/harness/providers/opencode.py b/sdk/python/agentfield/harness/providers/opencode.py index e022705a..6185fc17 100644 --- a/sdk/python/agentfield/harness/providers/opencode.py +++ b/sdk/python/agentfield/harness/providers/opencode.py @@ -26,13 +26,8 @@ class OpenCodeProvider: _MAX_CONCURRENT: ClassVar[int] = int(os.environ.get("OPENCODE_MAX_CONCURRENT", "3")) _concurrency_sem: ClassVar[Optional[asyncio.Semaphore]] = None - def __init__( - self, - bin_path: str = "opencode", - server_url: Optional[str] = None, - ): + def __init__(self, bin_path: str = "opencode"): self._bin = bin_path - self._explicit_server = server_url or os.environ.get("OPENCODE_SERVER") @classmethod def _get_semaphore(cls) -> asyncio.Semaphore: diff --git a/sdk/python/agentfield/types.py b/sdk/python/agentfield/types.py index 959cce81..23c00c36 100644 --- a/sdk/python/agentfield/types.py +++ b/sdk/python/agentfield/types.py @@ -326,15 +326,6 @@ class HarnessConfig(BaseModel): opencode_bin: str = Field( default="opencode", description="Path to opencode binary." ) - opencode_server: Optional[str] = Field( - default=None, - description=( - "URL of a running ``opencode serve`` instance " - '(e.g. "http://127.0.0.1:4096"). When set, the opencode provider ' - "uses ``--attach`` mode which avoids the standalone session bug. " - "Falls back to OPENCODE_SERVER env var." - ), - ) class AIConfig(BaseModel): diff --git a/sdk/python/tests/test_harness_provider_opencode.py b/sdk/python/tests/test_harness_provider_opencode.py index e6a2867d..75397164 100644 --- a/sdk/python/tests/test_harness_provider_opencode.py +++ b/sdk/python/tests/test_harness_provider_opencode.py @@ -29,7 +29,6 @@ async def fake_run_cli(cmd, *, env=None, cwd=None, timeout=None): provider = OpenCodeProvider( bin_path="/usr/local/bin/opencode", - server_url="http://127.0.0.1:9999", ) raw = await provider.execute( "hello", @@ -65,7 +64,6 @@ async def fake_run_cli(*_args, **_kwargs): provider = OpenCodeProvider( bin_path="opencode-missing", - server_url="http://127.0.0.1:9999", ) raw = await provider.execute("hello", {}) @@ -84,7 +82,7 @@ async def fake_run_cli(*_args, **_kwargs): monkeypatch.setattr("agentfield.harness.providers.opencode.run_cli", fake_run_cli) - provider = OpenCodeProvider(server_url="http://127.0.0.1:9999") + provider = OpenCodeProvider() raw = await provider.execute("hello", {}) assert raw.is_error is True @@ -97,13 +95,11 @@ def test_factory_builds_opencode_provider_with_config_bin() -> None: HarnessConfig( provider="opencode", opencode_bin="/opt/opencode", - opencode_server="http://127.0.0.1:4096", ) ) assert isinstance(provider, OpenCodeProvider) assert provider._bin == "/opt/opencode" - assert provider._explicit_server == "http://127.0.0.1:4096" @pytest.mark.asyncio @@ -117,7 +113,7 @@ async def fake_run_cli(cmd, *, env=None, cwd=None, timeout=None): monkeypatch.setattr("agentfield.harness.providers.opencode.run_cli", fake_run_cli) - provider = OpenCodeProvider(server_url="http://127.0.0.1:9999") + provider = OpenCodeProvider() raw = await provider.execute("hello", {"model": "openai/gpt-5"}) assert captured["cmd"] == [ @@ -143,7 +139,7 @@ async def fake_run_cli(cmd, *, env=None, cwd=None, timeout=None): with patch( "agentfield.harness.providers.opencode.estimate_cli_cost", return_value=0.0035 ): - provider = OpenCodeProvider(server_url="http://127.0.0.1:9999") + provider = OpenCodeProvider() raw = await provider.execute("hello", {"model": "openai/gpt-4o"}) assert raw.metrics.total_cost_usd == 0.0035 @@ -160,8 +156,33 @@ async def fake_run_cli(cmd, *, env=None, cwd=None, timeout=None): monkeypatch.setattr("agentfield.harness.providers.opencode.run_cli", fake_run_cli) - provider = OpenCodeProvider(server_url="http://127.0.0.1:9999") + provider = OpenCodeProvider() raw = await provider.execute("hello", {}) # No model → estimate_cli_cost gets empty string → returns None assert raw.metrics.total_cost_usd is None + + +@pytest.mark.asyncio +async def test_opencode_command_does_not_use_attach_pattern( + monkeypatch: pytest.MonkeyPatch, +): + """Verify the provider uses direct CLI pattern, NOT serve+attach workaround.""" + captured_cmd = None + + async def capture_cmd(cmd, *, env=None, cwd=None, timeout=None): + nonlocal captured_cmd + captured_cmd = cmd + return "result", "", 0 + + monkeypatch.setattr("agentfield.harness.providers.opencode.run_cli", capture_cmd) + + provider = OpenCodeProvider(bin_path="opencode") + await provider.execute("test prompt", {"model": "gpt-4"}) + + cmd_str = " ".join(captured_cmd) + assert "--attach" not in cmd_str + assert "http://" not in cmd_str + assert "127.0.0.1" not in cmd_str + assert "localhost" not in cmd_str + assert "opencode run" in cmd_str