mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 06:53:14 +00:00
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
Additive sandbox provider adapted to this fork's synchronous create_sandbox factory: agent/integrations/e2b.py registered as a lazy-import entry in SANDBOX_FACTORIES, so the e2b/langchain-e2b imports only load when SANDBOX_TYPE=e2b (dark-safe on unset env; unset E2B_API_KEY raises a clean ValueError only when the provider is selected). Supports reconnect-by-id and optional E2B_TEMPLATE. Non-langsmith providers skip the GitHub proxy step, so the GitHub-App token flow is untouched. Upstream: langchain-ai/open-swe 48217b68 (#1489), re-implemented against the fork's sync sandbox lifecycle rather than cherry-picked (upstream ships on the deferred async sandbox.py base). Docs: provider tables/lists in README, CUSTOMIZATION, INSTALLATION; the CUSTOMIZATION registration example now shows the lazy-tuple form the code actually uses. uv.lock refreshed (adds e2b, langchain-e2b, dockerfile-parse).
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
import importlib.util
|
|
import sys
|
|
import types
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class _FakeSandbox:
|
|
create_calls: list[dict[str, object]] = []
|
|
connect_calls: list[tuple[str, dict[str, object]]] = []
|
|
|
|
def __init__(self, sandbox_id: str):
|
|
self.sandbox_id = sandbox_id
|
|
|
|
@classmethod
|
|
def create(cls, **kwargs):
|
|
cls.create_calls.append(kwargs)
|
|
return cls("created-sandbox")
|
|
|
|
@classmethod
|
|
def connect(cls, sandbox_id: str, **kwargs):
|
|
cls.connect_calls.append((sandbox_id, kwargs))
|
|
return cls(sandbox_id)
|
|
|
|
|
|
class _FakeE2BSandbox:
|
|
def __init__(self, *, sandbox):
|
|
self.sandbox = sandbox
|
|
|
|
|
|
def _load_e2b_module(monkeypatch):
|
|
_FakeSandbox.create_calls = []
|
|
_FakeSandbox.connect_calls = []
|
|
|
|
fake_e2b = types.ModuleType("e2b")
|
|
fake_e2b.Sandbox = _FakeSandbox
|
|
|
|
fake_langchain_e2b = types.ModuleType("langchain_e2b")
|
|
fake_langchain_e2b.E2BSandbox = _FakeE2BSandbox
|
|
|
|
monkeypatch.setitem(sys.modules, "e2b", fake_e2b)
|
|
monkeypatch.setitem(sys.modules, "langchain_e2b", fake_langchain_e2b)
|
|
|
|
module_path = ROOT / "agent" / "integrations" / "e2b.py"
|
|
spec = importlib.util.spec_from_file_location("e2b_under_test", module_path)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_create_e2b_sandbox_uses_default_timeout(monkeypatch):
|
|
monkeypatch.setenv("E2B_API_KEY", "api-key")
|
|
monkeypatch.delenv("E2B_TEMPLATE", raising=False)
|
|
module = _load_e2b_module(monkeypatch)
|
|
|
|
backend = module.create_e2b_sandbox()
|
|
|
|
assert backend.sandbox.sandbox_id == "created-sandbox"
|
|
assert _FakeSandbox.create_calls == [{"timeout": 3600, "api_key": "api-key"}]
|
|
assert _FakeSandbox.connect_calls == []
|
|
|
|
|
|
def test_create_e2b_sandbox_uses_template(monkeypatch):
|
|
monkeypatch.setenv("E2B_API_KEY", "api-key")
|
|
monkeypatch.setenv("E2B_TEMPLATE", "open-swe-template")
|
|
module = _load_e2b_module(monkeypatch)
|
|
|
|
module.create_e2b_sandbox()
|
|
|
|
assert _FakeSandbox.create_calls == [
|
|
{"template": "open-swe-template", "timeout": 3600, "api_key": "api-key"}
|
|
]
|
|
|
|
|
|
def test_create_e2b_sandbox_reconnects_by_id(monkeypatch):
|
|
monkeypatch.setenv("E2B_API_KEY", "api-key")
|
|
module = _load_e2b_module(monkeypatch)
|
|
|
|
backend = module.create_e2b_sandbox("sbx_existing")
|
|
|
|
assert backend.sandbox.sandbox_id == "sbx_existing"
|
|
assert _FakeSandbox.connect_calls == [("sbx_existing", {"timeout": 3600, "api_key": "api-key"})]
|
|
assert _FakeSandbox.create_calls == []
|
|
|
|
|
|
def test_e2b_missing_api_key(monkeypatch):
|
|
monkeypatch.delenv("E2B_API_KEY", raising=False)
|
|
monkeypatch.delenv("E2B_TEMPLATE", raising=False)
|
|
module = _load_e2b_module(monkeypatch)
|
|
|
|
try:
|
|
module.create_e2b_sandbox()
|
|
except ValueError as exc:
|
|
assert "E2B_API_KEY" in str(exc)
|
|
else:
|
|
raise AssertionError("expected missing E2B_API_KEY to fail")
|
|
|
|
|
|
def test_e2b_rejects_empty_template(monkeypatch):
|
|
monkeypatch.setenv("E2B_API_KEY", "api-key")
|
|
monkeypatch.setenv("E2B_TEMPLATE", " ")
|
|
module = _load_e2b_module(monkeypatch)
|
|
|
|
try:
|
|
module.create_e2b_sandbox()
|
|
except ValueError as exc:
|
|
assert "E2B_TEMPLATE must not be empty" in str(exc)
|
|
else:
|
|
raise AssertionError("expected empty E2B_TEMPLATE to fail")
|