diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 948f6161..cf44671e 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -45,7 +45,6 @@ services: restart: on-failure environment: - TZ=Asia/Shanghai - - BOX__RUNTIME_URL=http://langbot_box_runtime:5410 ports: - 5300:5300 # For web ui and webhook callback - 2280-2285:2280-2285 # For platform reverse connection diff --git a/src/langbot/pkg/box/backend.py b/src/langbot/pkg/box/backend.py index 4db6525c..ea74a090 100644 --- a/src/langbot/pkg/box/backend.py +++ b/src/langbot/pkg/box/backend.py @@ -248,12 +248,6 @@ class CLISandboxBackend(BaseSandboxBackend): timed_out=False, ) - @staticmethod - def _clip_bytes(data: bytes, limit: int = _MAX_RAW_OUTPUT_BYTES) -> str: - """Decode bytes to str, discarding bytes beyond *limit*.""" - clipped = data[:limit] - return CLISandboxBackend._clip_captured_bytes(clipped, len(data), limit=limit) - @staticmethod def _clip_captured_bytes(data: bytes, total_size: int, limit: int = _MAX_RAW_OUTPUT_BYTES) -> str: text = data.decode('utf-8', errors='replace').strip() diff --git a/src/langbot/pkg/box/client.py b/src/langbot/pkg/box/client.py index f13d67d5..3e9808ca 100644 --- a/src/langbot/pkg/box/client.py +++ b/src/langbot/pkg/box/client.py @@ -1,4 +1,4 @@ -"""BoxRuntimeClient abstraction for local and remote Box Runtime access.""" +"""BoxRuntimeClient abstraction for remote Box Runtime access.""" from __future__ import annotations @@ -16,8 +16,7 @@ from .errors import ( BoxSessionNotFoundError, BoxValidationError, ) -from .models import BoxExecutionResult, BoxExecutionStatus, BoxSpec -from .runtime import BoxRuntime +from .models import BoxExecutionResult, BoxExecutionStatus, BoxSpec, get_box_config from ..utils import platform if TYPE_CHECKING: @@ -34,9 +33,7 @@ _ERROR_CODE_MAP: dict[str, type[BoxError]] = { def resolve_box_runtime_url(ap: 'core_app.Application') -> str: - box_config = getattr(ap, 'instance_config', None) - box_config_data = getattr(box_config, 'data', {}) if box_config is not None else {} - runtime_url = str(box_config_data.get('box', {}).get('runtime_url', '')).strip() + runtime_url = str(get_box_config(ap).get('runtime_url', '')).strip() if runtime_url: return runtime_url @@ -45,16 +42,6 @@ def resolve_box_runtime_url(ap: 'core_app.Application') -> str: return 'http://127.0.0.1:5410' -def create_box_runtime_client( - ap: 'core_app.Application', - runtime_url: str | None = None, -) -> 'RemoteBoxRuntimeClient': - return RemoteBoxRuntimeClient( - base_url=runtime_url or resolve_box_runtime_url(ap), - logger=ap.logger, - ) - - class BoxRuntimeClient(abc.ABC): """Abstract interface that BoxService uses to talk to a Box Runtime.""" @@ -83,41 +70,6 @@ class BoxRuntimeClient(abc.ABC): async def create_session(self, spec: BoxSpec) -> dict: ... -class LocalBoxRuntimeClient(BoxRuntimeClient): - """In-process client that wraps a real BoxRuntime directly.""" - - def __init__(self, logger: logging.Logger, runtime: BoxRuntime | None = None): - self._runtime = runtime or BoxRuntime(logger=logger) - - @property - def runtime(self) -> BoxRuntime: - return self._runtime - - async def initialize(self) -> None: - await self._runtime.initialize() - - async def execute(self, spec: BoxSpec) -> BoxExecutionResult: - return await self._runtime.execute(spec) - - async def shutdown(self) -> None: - await self._runtime.shutdown() - - async def get_status(self) -> dict: - return await self._runtime.get_status() - - async def get_sessions(self) -> list[dict]: - return self._runtime.get_sessions() - - async def get_backend_info(self) -> dict: - return await self._runtime.get_backend_info() - - async def delete_session(self, session_id: str) -> None: - await self._runtime.delete_session(session_id) - - async def create_session(self, spec: BoxSpec) -> dict: - return await self._runtime.create_session(spec) - - class RemoteBoxRuntimeClient(BoxRuntimeClient): """HTTP client that talks to a standalone Box Runtime service.""" diff --git a/src/langbot/pkg/box/connector.py b/src/langbot/pkg/box/connector.py index f05299cd..e1b83f9e 100644 --- a/src/langbot/pkg/box/connector.py +++ b/src/langbot/pkg/box/connector.py @@ -6,7 +6,8 @@ import sys from typing import TYPE_CHECKING from .errors import BoxRuntimeUnavailableError -from .client import create_box_runtime_client, resolve_box_runtime_url +from .client import RemoteBoxRuntimeClient, resolve_box_runtime_url +from .models import get_box_config from ..utils import platform if TYPE_CHECKING: @@ -24,7 +25,7 @@ class BoxRuntimeConnector: self.configured_runtime_url = self._load_configured_runtime_url() self.runtime_url = self.configured_runtime_url or resolve_box_runtime_url(ap) self.manages_local_runtime = self._should_manage_local_runtime() - self.client = create_box_runtime_client(ap, runtime_url=self.runtime_url) + self.client = RemoteBoxRuntimeClient(base_url=self.runtime_url, logger=ap.logger) self.runtime_subprocess: asyncio.subprocess.Process | None = None self.runtime_subprocess_task: asyncio.Task | None = None @@ -54,9 +55,7 @@ class BoxRuntimeConnector: self.runtime_subprocess_task = None def _load_configured_runtime_url(self) -> str: - box_config = getattr(self.ap, 'instance_config', None) - box_config_data = getattr(box_config, 'data', {}) if box_config is not None else {} - return str(box_config_data.get('box', {}).get('runtime_url', '')).strip() + return str(get_box_config(self.ap).get('runtime_url', '')).strip() def _should_manage_local_runtime(self) -> bool: return not self.configured_runtime_url and platform.get_platform() != 'docker' diff --git a/src/langbot/pkg/box/models.py b/src/langbot/pkg/box/models.py index e99c85b3..64f71f4a 100644 --- a/src/langbot/pkg/box/models.py +++ b/src/langbot/pkg/box/models.py @@ -10,6 +10,13 @@ DEFAULT_BOX_IMAGE = 'python:3.11-slim' DEFAULT_BOX_MOUNT_PATH = '/workspace' +def get_box_config(ap) -> dict: + """Return the 'box' section from instance config, with safe fallbacks.""" + instance_config = getattr(ap, 'instance_config', None) + config_data = getattr(instance_config, 'data', {}) if instance_config is not None else {} + return config_data.get('box', {}) + + class BoxNetworkMode(str, enum.Enum): OFF = 'off' ON = 'on' @@ -26,7 +33,7 @@ class BoxHostMountMode(str, enum.Enum): class BoxSpec(pydantic.BaseModel): - cmd: str + cmd: str = '' workdir: str = '/workspace' timeout_sec: int = 30 network: BoxNetworkMode = BoxNetworkMode.OFF @@ -44,10 +51,7 @@ class BoxSpec(pydantic.BaseModel): @pydantic.field_validator('cmd') @classmethod def validate_cmd(cls, value: str) -> str: - value = value.strip() - if not value: - raise ValueError('cmd must not be empty') - return value + return value.strip() @pydantic.field_validator('workdir') @classmethod diff --git a/src/langbot/pkg/box/runtime.py b/src/langbot/pkg/box/runtime.py index 39342f12..89ad8c0b 100644 --- a/src/langbot/pkg/box/runtime.py +++ b/src/langbot/pkg/box/runtime.py @@ -6,7 +6,7 @@ import datetime as dt import logging from .backend import BaseSandboxBackend, DockerBackend, PodmanBackend -from .errors import BoxBackendUnavailableError, BoxSessionConflictError, BoxSessionNotFoundError +from .errors import BoxBackendUnavailableError, BoxSessionConflictError, BoxSessionNotFoundError, BoxValidationError from .models import BoxExecutionResult, BoxExecutionStatus, BoxSessionInfo, BoxSpec _UTC = dt.timezone.utc @@ -36,6 +36,8 @@ class BoxRuntime: self._backend = await self._select_backend() async def execute(self, spec: BoxSpec) -> BoxExecutionResult: + if not spec.cmd: + raise BoxValidationError('cmd must not be empty') session = await self._get_or_create_session(spec) async with session.lock: @@ -183,38 +185,18 @@ class BoxRuntime: self.logger.warning(f'Failed to clean up box session {session_id}: {exc}') def _assert_session_compatible(self, session: BoxSessionInfo, spec: BoxSpec): - if session.network != spec.network: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with network={session.network.value}' - ) - if session.image != spec.image: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with image={session.image}' - ) - if session.host_path != spec.host_path: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with host_path={session.host_path}' - ) - if session.host_path_mode != spec.host_path_mode: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with host_path_mode={session.host_path_mode.value}' - ) - if session.cpus != spec.cpus: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with cpus={session.cpus}' - ) - if session.memory_mb != spec.memory_mb: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with memory_mb={session.memory_mb}' - ) - if session.pids_limit != spec.pids_limit: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with pids_limit={session.pids_limit}' - ) - if session.read_only_rootfs != spec.read_only_rootfs: - raise BoxSessionConflictError( - f'sandbox_exec session {spec.session_id} already exists with read_only_rootfs={session.read_only_rootfs}' - ) + _COMPAT_FIELDS = ( + 'network', 'image', 'host_path', 'host_path_mode', + 'cpus', 'memory_mb', 'pids_limit', 'read_only_rootfs', + ) + for field in _COMPAT_FIELDS: + session_val = getattr(session, field) + spec_val = getattr(spec, field) + if session_val != spec_val: + display = session_val.value if hasattr(session_val, 'value') else session_val + raise BoxSessionConflictError( + f'sandbox_exec session {spec.session_id} already exists with {field}={display}' + ) @staticmethod def _session_to_dict(info: BoxSessionInfo) -> dict: diff --git a/src/langbot/pkg/box/server.py b/src/langbot/pkg/box/server.py index 67b78cec..52907c8e 100644 --- a/src/langbot/pkg/box/server.py +++ b/src/langbot/pkg/box/server.py @@ -81,7 +81,6 @@ async def handle_create_session(request: web.Request) -> web.Response: body = await request.json() session_id = request.match_info['session_id'] body['session_id'] = session_id - body.setdefault('cmd', '__langbot_session_placeholder__') spec = BoxSpec.model_validate(body) session_info = await runtime.create_session(spec) return web.json_response(session_info, status=201) diff --git a/src/langbot/pkg/box/service.py b/src/langbot/pkg/box/service.py index b7dc412c..4224521c 100644 --- a/src/langbot/pkg/box/service.py +++ b/src/langbot/pkg/box/service.py @@ -12,7 +12,7 @@ import pydantic from .client import BoxRuntimeClient from .connector import BoxRuntimeConnector from .errors import BoxError, BoxValidationError -from .models import BUILTIN_PROFILES, BoxExecutionResult, BoxProfile, BoxSpec +from .models import BUILTIN_PROFILES, BoxExecutionResult, BoxProfile, BoxSpec, get_box_config _INT_ADAPTER = pydantic.TypeAdapter(int) _UTC = _dt.timezone.utc @@ -189,9 +189,7 @@ class BoxService: } def _load_allowed_host_mount_roots(self) -> list[str]: - box_config = getattr(self.ap, 'instance_config', None) - box_config_data = getattr(box_config, 'data', {}) if box_config is not None else {} - configured_roots = box_config_data.get('box', {}).get('allowed_host_mount_roots', []) + configured_roots = get_box_config(self.ap).get('allowed_host_mount_roots', []) normalized_roots: list[str] = [] for root in configured_roots: @@ -203,9 +201,7 @@ class BoxService: return normalized_roots def _load_default_host_workspace(self) -> str | None: - box_config = getattr(self.ap, 'instance_config', None) - box_config_data = getattr(box_config, 'data', {}) if box_config is not None else {} - default_host_workspace = str(box_config_data.get('box', {}).get('default_host_workspace', '')).strip() + default_host_workspace = str(get_box_config(self.ap).get('default_host_workspace', '')).strip() if not default_host_workspace: return None return os.path.realpath(os.path.abspath(default_host_workspace)) @@ -252,9 +248,7 @@ class BoxService: raise BoxValidationError(f'host_path is outside allowed_host_mount_roots: {allowed_roots}') def _load_profile(self) -> BoxProfile: - box_config = getattr(self.ap, 'instance_config', None) - box_config_data = getattr(box_config, 'data', {}) if box_config is not None else {} - profile_name = str(box_config_data.get('box', {}).get('profile', 'default')).strip() or 'default' + profile_name = str(get_box_config(self.ap).get('profile', 'default')).strip() or 'default' profile = BUILTIN_PROFILES.get(profile_name) if profile is None: diff --git a/src/langbot/pkg/provider/tools/loaders/native.py b/src/langbot/pkg/provider/tools/loaders/native.py index 6087351e..22e696d9 100644 --- a/src/langbot/pkg/provider/tools/loaders/native.py +++ b/src/langbot/pkg/provider/tools/loaders/native.py @@ -28,8 +28,7 @@ class NativeToolLoader(loader.ToolLoader): return await self.ap.box_service.execute_sandbox_tool(parameters, query) async def shutdown(self): - if getattr(self.ap, 'box_service', None) is not None: - await self.ap.box_service.shutdown() + pass def _build_sandbox_exec_tool(self) -> resource_tool.LLMTool: return resource_tool.LLMTool( @@ -64,23 +63,6 @@ class NativeToolLoader(loader.ToolLoader): 'enum': ['off', 'on'], 'default': 'off', }, - 'session_id': { - 'type': 'string', - 'description': 'Optional sandbox session id. Defaults to the current request id for reuse.', - }, - 'host_path': { - 'type': 'string', - 'description': ( - 'Optional absolute host directory path to mount into the sandbox as /workspace. ' - 'The path must be under an allowed host mount root.' - ), - }, - 'host_path_mode': { - 'type': 'string', - 'description': 'Mount mode for host_path. Use rw to create or modify host files.', - 'enum': ['ro', 'rw'], - 'default': 'rw', - }, 'env': { 'type': 'object', 'description': 'Optional environment variables to expose inside the sandbox.', diff --git a/tests/unit_tests/box/test_backend_clip.py b/tests/unit_tests/box/test_backend_clip.py index af593abe..f6ea07b2 100644 --- a/tests/unit_tests/box/test_backend_clip.py +++ b/tests/unit_tests/box/test_backend_clip.py @@ -5,33 +5,34 @@ import pytest from langbot.pkg.box.backend import CLISandboxBackend, _MAX_RAW_OUTPUT_BYTES -class TestClipBytes: +class TestClipCapturedBytes: def test_within_limit_unchanged(self): data = b'hello world' - result = CLISandboxBackend._clip_bytes(data, limit=1024) + result = CLISandboxBackend._clip_captured_bytes(data, total_size=len(data), limit=1024) assert result == 'hello world' def test_exceeding_limit_clips_and_appends_notice(self): - data = b'A' * 200 - result = CLISandboxBackend._clip_bytes(data, limit=100) + captured = b'A' * 100 + total_size = 200 + result = CLISandboxBackend._clip_captured_bytes(captured, total_size=total_size, limit=100) assert result.startswith('A' * 100) assert 'raw output clipped at 100 bytes' in result assert '100 bytes discarded' in result def test_exact_limit_not_clipped(self): data = b'B' * 100 - result = CLISandboxBackend._clip_bytes(data, limit=100) + result = CLISandboxBackend._clip_captured_bytes(data, total_size=100, limit=100) assert result == 'B' * 100 assert 'clipped' not in result def test_default_limit_is_module_constant(self): data = b'x' * 10 - result = CLISandboxBackend._clip_bytes(data) + result = CLISandboxBackend._clip_captured_bytes(data, total_size=10) assert result == 'x' * 10 assert _MAX_RAW_OUTPUT_BYTES == 1_048_576 def test_invalid_utf8_replaced(self): data = b'ok\xff\xfetail' - result = CLISandboxBackend._clip_bytes(data, limit=1024) + result = CLISandboxBackend._clip_captured_bytes(data, total_size=len(data), limit=1024) assert 'ok' in result assert 'tail' in result diff --git a/tests/unit_tests/box/test_box_service.py b/tests/unit_tests/box/test_box_service.py index bc43f345..c4ce9f5c 100644 --- a/tests/unit_tests/box/test_box_service.py +++ b/tests/unit_tests/box/test_box_service.py @@ -12,7 +12,7 @@ import pytest import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query from langbot.pkg.box.backend import BaseSandboxBackend -from langbot.pkg.box.client import LocalBoxRuntimeClient, RemoteBoxRuntimeClient +from langbot.pkg.box.client import BoxRuntimeClient, RemoteBoxRuntimeClient from langbot.pkg.box.errors import BoxBackendUnavailableError, BoxSessionConflictError, BoxSessionNotFoundError, BoxValidationError from langbot.pkg.box.models import ( BUILTIN_PROFILES, @@ -30,6 +30,37 @@ from langbot.pkg.box.service import BoxService _UTC = dt.timezone.utc +class _InProcessBoxRuntimeClient(BoxRuntimeClient): + """Test-only client that wraps a BoxRuntime in-process (no HTTP).""" + + def __init__(self, logger, runtime=None): + self._runtime = runtime or BoxRuntime(logger=logger) + + async def initialize(self): + await self._runtime.initialize() + + async def execute(self, spec): + return await self._runtime.execute(spec) + + async def shutdown(self): + await self._runtime.shutdown() + + async def get_status(self): + return await self._runtime.get_status() + + async def get_sessions(self): + return self._runtime.get_sessions() + + async def get_backend_info(self): + return await self._runtime.get_backend_info() + + async def delete_session(self, session_id): + await self._runtime.delete_session(session_id) + + async def create_session(self, spec): + return await self._runtime.create_session(spec) + + def _can_open_test_socket() -> bool: try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -197,7 +228,7 @@ async def test_box_service_defaults_session_id_from_query(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'pwd', 'network': BoxNetworkMode.OFF.value}, make_query(7)) @@ -212,7 +243,7 @@ async def test_box_service_fails_closed_when_backend_unavailable(): logger = Mock() backend = FakeBackend(logger, available=False) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() with pytest.raises(BoxBackendUnavailableError): @@ -226,7 +257,7 @@ async def test_box_service_allows_host_mount_under_configured_root(tmp_path): runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) host_dir = tmp_path / 'mounted-workspace' host_dir.mkdir() - service = BoxService(make_app(logger, [str(tmp_path)]), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger, [str(tmp_path)]), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool( @@ -251,7 +282,7 @@ async def test_box_service_uses_default_host_workspace_when_host_path_omitted(tm host_dir.mkdir() app = make_app(logger, [str(tmp_path)]) app.instance_config.data['box']['default_host_workspace'] = str(host_dir) - service = BoxService(app, client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(app, client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'pwd'}, make_query(15)) @@ -272,7 +303,7 @@ async def test_box_service_creates_default_host_workspace_on_initialize(tmp_path default_host_workspace = allowed_root / 'default-workspace' app = make_app(logger, [str(allowed_root)]) app.instance_config.data['box']['default_host_workspace'] = str(default_host_workspace) - service = BoxService(app, client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(app, client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() @@ -288,7 +319,7 @@ async def test_box_service_rejects_host_mount_outside_allowed_roots(tmp_path): disallowed_root = tmp_path / 'disallowed' allowed_root.mkdir() disallowed_root.mkdir() - service = BoxService(make_app(logger, [str(allowed_root)]), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger, [str(allowed_root)]), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() with pytest.raises(BoxValidationError): @@ -379,7 +410,7 @@ async def test_truncate_short_output_unchanged(): logger = Mock() backend = FakeBackendWithOutput(logger, stdout='hello world') runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime), output_limit_chars=100) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime), output_limit_chars=100) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'echo hello'}, make_query(20)) @@ -400,7 +431,7 @@ async def test_truncate_preserves_head_and_tail(): backend = FakeBackendWithOutput(logger, stdout=big_output) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) limit = 100 - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime), output_limit_chars=limit) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime), output_limit_chars=limit) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'cat big'}, make_query(21)) @@ -422,7 +453,7 @@ async def test_truncate_at_exact_limit_not_truncated(): exact_output = 'a' * 200 backend = FakeBackendWithOutput(logger, stdout=exact_output) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime), output_limit_chars=200) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime), output_limit_chars=200) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'echo a'}, make_query(22)) @@ -436,7 +467,7 @@ async def test_truncate_stderr_independently(): logger = Mock() backend = FakeBackendWithOutput(logger, stdout='short', stderr='E' * 300) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime), output_limit_chars=100) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime), output_limit_chars=100) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'fail'}, make_query(23)) @@ -456,7 +487,7 @@ async def test_profile_default_provides_defaults(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool({'cmd': 'echo hi'}, make_query(30)) @@ -474,7 +505,7 @@ async def test_profile_unlocked_field_can_be_overridden(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool( @@ -494,7 +525,7 @@ async def test_profile_locked_field_cannot_be_overridden(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger, profile='offline_readonly'), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger, profile='offline_readonly'), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool( @@ -514,7 +545,7 @@ async def test_profile_timeout_clamped_to_max(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() result = await service.execute_sandbox_tool( @@ -534,7 +565,7 @@ async def test_profile_timeout_clamped_for_coercible_inputs(timeout_value): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() await service.execute_sandbox_tool( @@ -551,7 +582,7 @@ def test_unknown_profile_raises_error(): logger = Mock() runtime = BoxRuntime(logger=logger, backends=[FakeBackend(logger)], session_ttl_sec=300) with pytest.raises(BoxValidationError, match='unknown box profile'): - BoxService(make_app(logger, profile='nonexistent'), client=LocalBoxRuntimeClient(logger, runtime)) + BoxService(make_app(logger, profile='nonexistent'), client=_InProcessBoxRuntimeClient(logger, runtime)) def test_builtin_profiles_are_consistent(): @@ -586,7 +617,7 @@ async def test_profile_default_applies_resource_limits(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() await service.execute_sandbox_tool({'cmd': 'echo hi'}, make_query(40)) @@ -605,7 +636,7 @@ async def test_profile_offline_readonly_locks_read_only_rootfs(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger, profile='offline_readonly'), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger, profile='offline_readonly'), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() await service.execute_sandbox_tool( @@ -623,7 +654,7 @@ async def test_profile_network_extended_has_relaxed_limits(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger, profile='network_extended'), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger, profile='network_extended'), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() await service.execute_sandbox_tool({'cmd': 'echo hi'}, make_query(42)) @@ -698,7 +729,7 @@ async def test_service_records_errors_on_failure(): logger = Mock() backend = FakeBackend(logger, available=False) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() with pytest.raises(Exception): @@ -716,7 +747,7 @@ async def test_service_error_ring_buffer_capped(): logger = Mock() backend = FakeBackend(logger, available=False) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() for i in range(60): @@ -735,7 +766,7 @@ async def test_service_get_status_aggregates_runtime_and_profile(): logger = Mock() backend = FakeBackend(logger) runtime = BoxRuntime(logger=logger, backends=[backend], session_ttl_sec=300) - service = BoxService(make_app(logger), client=LocalBoxRuntimeClient(logger, runtime)) + service = BoxService(make_app(logger), client=_InProcessBoxRuntimeClient(logger, runtime)) await service.initialize() status = await service.get_status()