feat: add sandbox filesystem and terminal support

Add sandbox filesystem methods (list_dir, mkdir, remove, upload,
download, stream_upload, stream_download) and interactive PTY sessions
(PtySession, AsyncPtySession) with reconnect support per
FILE_TERMINAL.md spec. Refactor error handling into exceptions.py as
shared handle_response(). Replace API-key-only proxy auth with unified
_proxy_headers() supporting both API key and JWT. Fix stream_upload to
build multipart manually instead of relying on httpx files= with
generators. Switch Makefile SPEC_URL from main to dev branch. Regenerate
models from updated OpenAPI spec (adds teams, channels, metrics, PTY
endpoints). Add comprehensive unit and integration tests. Trim AGENTS.md
to verified facts only.
This commit is contained in:
Tasnim Kabir Sadik
2026-04-12 02:35:20 +06:00
parent f51a962fff
commit a5bf66c199
13 changed files with 3180 additions and 445 deletions

View File

@ -5,7 +5,6 @@ import pytest
import respx
from wrenn.client import WrennClient
from wrenn.exceptions import WrennAuthenticationError
from wrenn.sandbox import CodeResult, Sandbox, _build_proxy_url
@ -57,22 +56,6 @@ class TestSandboxGetUrl:
assert url == "ws://3000-cl-xyz.localhost:8080"
class TestProxyAuthGuard:
def test_jwt_only_get_url_raises(self):
with WrennClient(token="jwt-abc") as c:
sb = Sandbox(id="cl-abc")
sb._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
with pytest.raises(WrennAuthenticationError):
sb.get_url(8888)
def test_jwt_only_http_client_raises(self):
with WrennClient(token="jwt-abc") as c:
sb = Sandbox(id="cl-abc")
sb._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
with pytest.raises(WrennAuthenticationError):
_ = sb.http_client
class TestSandboxHttpClient:
@respx.mock
def test_http_client_has_api_key_header(self, client):
@ -96,6 +79,20 @@ class TestSandboxHttpClient:
assert resp.status_code == 200
assert route.called
def test_jwt_only_get_url_works(self):
with WrennClient(token="jwt-abc") as c:
sb = Sandbox(id="cl-abc")
sb._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
url = sb.get_url(8888)
assert "8888-cl-abc" in url
def test_jwt_only_http_client_has_bearer_header(self):
with WrennClient(token="jwt-abc") as c:
sb = Sandbox(id="cl-abc")
sb._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
hc = sb.http_client
assert hc.headers["Authorization"] == "Bearer jwt-abc"
class TestCreateReturnsBoundSandbox:
@respx.mock
@ -148,15 +145,6 @@ class TestCodeResult:
assert "ZeroDivisionError" in r.error
class TestRunCodeAuthGuard:
def test_jwt_only_run_code_raises(self):
with WrennClient(token="jwt-abc") as c:
sb = Sandbox(id="cl-abc")
sb._bind(c._http, str(c._http.base_url), api_key=None, token="jwt-abc")
with pytest.raises(WrennAuthenticationError):
sb.run_code("print(1)")
class TestJupyterMessageFormat:
def test_execute_request_structure(self):
sb = Sandbox(id="test")