mirror of
https://github.com/usestrix/strix.git
synced 2026-08-16 09:26:39 +02:00
feat(runtime): resolve sandbox ports over a shared Docker network (#775)
Co-authored-by: Ahmed Allam <ahmed39652003@gmail.com>
This commit is contained in:
co-authored by
Ahmed Allam
parent
40f4e67320
commit
914207ffb3
@@ -24,18 +24,22 @@ from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from agents.sandbox.errors import ExposedPortUnavailableError
|
||||
from agents.sandbox.manifest import Manifest
|
||||
from agents.sandbox.sandboxes.docker import (
|
||||
DockerSandboxClient,
|
||||
DockerSandboxSession,
|
||||
_build_docker_volume_mounts,
|
||||
_docker_port_key,
|
||||
_manifest_requires_fuse,
|
||||
_manifest_requires_sys_admin,
|
||||
)
|
||||
from agents.sandbox.session.sandbox_session import SandboxSession
|
||||
from agents.sandbox.types import ExposedPortEndpoint
|
||||
from docker import errors as docker_errors # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.models.containers import Container # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.types import Mount as DockerSDKMount # type: ignore[import-untyped, unused-ignore]
|
||||
@@ -46,6 +50,59 @@ from requests.exceptions import RequestException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_SANDBOX_NETWORK_ENV = "STRIX_DOCKER_SANDBOX_NETWORK"
|
||||
|
||||
|
||||
def _sandbox_network() -> str | None:
|
||||
value = os.environ.get(_SANDBOX_NETWORK_ENV, "").strip()
|
||||
return value or None
|
||||
|
||||
|
||||
def _apply_sandbox_network(create_kwargs: dict[str, Any]) -> None:
|
||||
network = _sandbox_network()
|
||||
if network:
|
||||
create_kwargs["network"] = network
|
||||
create_kwargs.pop("ports", None)
|
||||
|
||||
|
||||
class StrixDockerSandboxSession(DockerSandboxSession):
|
||||
sandbox_network: str = ""
|
||||
|
||||
async def _resolve_exposed_port(self, port: int) -> ExposedPortEndpoint:
|
||||
try:
|
||||
self._container.reload()
|
||||
except docker_errors.APIError as e:
|
||||
raise ExposedPortUnavailableError(
|
||||
port=port,
|
||||
exposed_ports=self.state.exposed_ports,
|
||||
reason="backend_unavailable",
|
||||
context={
|
||||
"backend": "docker",
|
||||
"detail": "container_reload_failed",
|
||||
"network": self.sandbox_network,
|
||||
},
|
||||
cause=e,
|
||||
) from e
|
||||
|
||||
attrs = getattr(self._container, "attrs", {}) or {}
|
||||
networks = attrs.get("NetworkSettings", {}).get("Networks", {})
|
||||
endpoint = networks.get(self.sandbox_network) or {}
|
||||
ip = endpoint.get("IPAddress") or endpoint.get("GlobalIPv6Address")
|
||||
if not isinstance(ip, str) or not ip:
|
||||
raise ExposedPortUnavailableError(
|
||||
port=port,
|
||||
exposed_ports=self.state.exposed_ports,
|
||||
reason="backend_unavailable",
|
||||
context={
|
||||
"backend": "docker",
|
||||
"detail": "container_not_on_network",
|
||||
"network": self.sandbox_network,
|
||||
},
|
||||
)
|
||||
host = f"[{ip}]" if ":" in ip else ip
|
||||
return ExposedPortEndpoint(host=host, port=port, tls=False)
|
||||
|
||||
|
||||
class StrixDockerSandboxClient(DockerSandboxClient):
|
||||
# Host directories to bind-mount into the container, set by the docker
|
||||
# backend before ``create()``. Each item is ``{source, target, read_only}``.
|
||||
@@ -117,6 +174,8 @@ class StrixDockerSandboxClient(DockerSandboxClient):
|
||||
extra_hosts = create_kwargs.setdefault("extra_hosts", {})
|
||||
extra_hosts["host.docker.internal"] = "host-gateway"
|
||||
|
||||
_apply_sandbox_network(create_kwargs)
|
||||
|
||||
# Strix injection: host bind mounts (e.g. large repos passed via --mount)
|
||||
# that bypass the SDK's file-by-file LocalDir copy.
|
||||
bind_mounts = getattr(self, "strix_bind_mounts", ())
|
||||
@@ -146,6 +205,15 @@ class StrixDockerSandboxClient(DockerSandboxClient):
|
||||
)
|
||||
return container
|
||||
|
||||
async def create(self, **kwargs: Any) -> SandboxSession:
|
||||
session = await super().create(**kwargs)
|
||||
network = _sandbox_network()
|
||||
inner = session._inner
|
||||
if network and isinstance(inner, DockerSandboxSession):
|
||||
inner.__class__ = StrixDockerSandboxSession
|
||||
cast("StrixDockerSandboxSession", inner).sandbox_network = network
|
||||
return session
|
||||
|
||||
async def delete(self, session: SandboxSession) -> SandboxSession:
|
||||
container_id = getattr(getattr(session._inner, "state", None), "container_id", None)
|
||||
if container_id:
|
||||
|
||||
Reference in New Issue
Block a user