Files
2026-07-13 12:39:17 +08:00

1073 lines
33 KiB
Python

from __future__ import annotations
import dataclasses
import uuid
from collections.abc import Iterable
from typing import Any, TypeVar, cast
import pytest
from pydantic import TypeAdapter
import agents.sandbox as sandbox_package
import agents.sandbox.capabilities as capabilities_package
import agents.sandbox.entries as entries_package
import agents.sandbox.session as session_package
from agents import Agent
from agents.run_config import SandboxArchiveLimits, SandboxConcurrencyLimits, SandboxRunConfig
from agents.run_context import RunContextWrapper
from agents.run_state import RunState
from agents.sandbox import Manifest
from agents.sandbox.entries import (
AzureBlobMount,
Dir,
DockerVolumeMountStrategy,
File,
GCSMount,
GitRepo,
InContainerMountStrategy,
LocalDir,
LocalFile,
MountPattern,
R2Mount,
S3FilesMount,
S3Mount,
)
from agents.sandbox.entries.base import BaseEntry
from agents.sandbox.entries.mounts.base import MountStrategyBase
from agents.sandbox.entries.mounts.patterns import (
FuseMountPattern,
MountpointMountPattern,
RcloneMountPattern,
S3FilesMountPattern,
)
from agents.sandbox.session.sandbox_client import BaseSandboxClientOptions
from agents.sandbox.session.sandbox_session_state import SandboxSessionState
from agents.sandbox.snapshot import LocalSnapshot, NoopSnapshot, RemoteSnapshot, SnapshotBase
from tests.utils.factories import TestSessionState
StateT = TypeVar("StateT", bound=SandboxSessionState)
def _session_state_kwargs() -> dict[str, object]:
return {
"session_id": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
"snapshot": NoopSnapshot(id="snapshot-123"),
"manifest": Manifest(root="/workspace"),
"exposed_ports": (8000,),
"workspace_root_ready": True,
}
def _make_session_state(cls: type[StateT], **overrides: object) -> StateT:
return cls.model_validate({**_session_state_kwargs(), **overrides})
def _import_optional_class(module_name: str, class_name: str) -> type[Any]:
module = pytest.importorskip(module_name)
value = getattr(module, class_name)
assert isinstance(value, type)
return cast(type[Any], value)
def _instantiate_optional_class(
module_name: str,
class_name: str,
*args: object,
**kwargs: object,
) -> Any:
cls = _import_optional_class(module_name, class_name)
return cls(*args, **kwargs)
def _make_optional_session_state(
module_name: str,
class_name: str,
**overrides: object,
) -> SandboxSessionState:
cls = _import_optional_class(module_name, class_name)
return cast(SandboxSessionState, cls.model_validate({**_session_state_kwargs(), **overrides}))
def test_core_sandbox_public_export_surface_is_stable() -> None:
expected_exports = {
"agents.sandbox": {
"Capability",
"Dir",
"ErrorCode",
"ExecResult",
"ExposedPortEndpoint",
"ExposedPortUnavailableError",
"ExecTimeoutError",
"ExecTransportError",
"FileMode",
"Group",
"LocalFile",
"LocalSnapshot",
"LocalSnapshotSpec",
"Manifest",
"MemoryLayoutConfig",
"MemoryReadConfig",
"MemoryGenerateConfig",
"RemoteSnapshot",
"RemoteSnapshotSpec",
"Permissions",
"SandboxAgent",
"SandboxArchiveLimits",
"SandboxPathGrant",
"SandboxConcurrencyLimits",
"SandboxError",
"SandboxRunConfig",
"SnapshotSpec",
"WorkspaceArchiveReadError",
"WorkspaceArchiveWriteError",
"WorkspaceReadNotFoundError",
"WorkspaceWriteTypeError",
"User",
"resolve_snapshot",
},
"agents.sandbox.entries": {
"AzureBlobMount",
"BaseEntry",
"BoxMount",
"Dir",
"File",
"DockerVolumeMountStrategy",
"FuseMountPattern",
"GCSMount",
"GitRepo",
"InContainerMountStrategy",
"LocalDir",
"LocalFile",
"Mount",
"MountPattern",
"MountPatternBase",
"MountStrategy",
"MountStrategyBase",
"MountpointMountPattern",
"R2Mount",
"RcloneMountPattern",
"S3Mount",
"S3FilesMount",
"S3FilesMountPattern",
"resolve_workspace_path",
},
"agents.sandbox.capabilities": {
"Capability",
"Capabilities",
"Compaction",
"CompactionModelInfo",
"CompactionPolicy",
"DynamicCompactionPolicy",
"FilesystemToolSet",
"LazySkillSource",
"LocalDirLazySkillSource",
"Memory",
"Shell",
"ShellToolSet",
"Skill",
"SkillMetadata",
"Skills",
"StaticCompactionPolicy",
"Filesystem",
},
"agents.sandbox.session": {
"BaseSandboxClient",
"BaseSandboxClientOptions",
"BaseSandboxSession",
"CallbackSink",
"ChainedSink",
"ClientOptionsT",
"Dependencies",
"DependenciesBindingError",
"DependenciesError",
"DependenciesMissingDependencyError",
"DependencyKey",
"ExposedPortEndpoint",
"EventPayloadPolicy",
"EventSink",
"HttpProxySink",
"Instrumentation",
"JsonlOutboxSink",
"SandboxSession",
"SandboxSessionEvent",
"SandboxSessionFinishEvent",
"SandboxSessionStartEvent",
"SandboxSessionState",
"WorkspaceJsonlSink",
"event_to_json_line",
"validate_sandbox_session_event",
},
}
modules = {
"agents.sandbox": sandbox_package,
"agents.sandbox.entries": entries_package,
"agents.sandbox.capabilities": capabilities_package,
"agents.sandbox.session": session_package,
}
for module_name, exports in expected_exports.items():
module = modules[module_name]
assert set(module.__all__) == exports
for name in exports:
assert getattr(module, name) is not None
@pytest.mark.parametrize(
("module_name", "expected_exports"),
[
(
"agents.extensions.sandbox.e2b",
{
"_E2BSandboxFactoryAPI",
"_encode_e2b_snapshot_ref",
"_import_sandbox_class",
"_sandbox_connect",
"E2BCloudBucketMountStrategy",
"E2BSandboxClient",
"E2BSandboxClientOptions",
"E2BSandboxSession",
"E2BSandboxSessionState",
"E2BSandboxTimeouts",
"E2BSandboxType",
},
),
(
"agents.extensions.sandbox.modal",
{
"_DEFAULT_TIMEOUT_S",
"_MODAL_STDIN_CHUNK_SIZE",
"_encode_modal_snapshot_ref",
"_encode_snapshot_directory_ref",
"_encode_snapshot_filesystem_ref",
"ModalCloudBucketMountConfig",
"ModalCloudBucketMountStrategy",
"ModalImageSelector",
"ModalSandboxClient",
"ModalSandboxClientOptions",
"ModalSandboxSelector",
"ModalSandboxSession",
"ModalSandboxSessionState",
"resolve_snapshot",
"tarfile",
},
),
(
"agents.extensions.sandbox.daytona",
{
"DEFAULT_DAYTONA_WORKSPACE_ROOT",
"DaytonaCloudBucketMountStrategy",
"DaytonaSandboxResources",
"DaytonaSandboxClient",
"DaytonaSandboxClientOptions",
"DaytonaSandboxSession",
"DaytonaSandboxSessionState",
"DaytonaSandboxTimeouts",
"ExposedPortUnavailableError",
"InvalidManifestPathError",
"WorkspaceArchiveReadError",
},
),
(
"agents.extensions.sandbox.blaxel",
{
"DEFAULT_BLAXEL_WORKSPACE_ROOT",
"BlaxelCloudBucketMountConfig",
"BlaxelCloudBucketMountStrategy",
"BlaxelDriveMount",
"BlaxelDriveMountConfig",
"BlaxelDriveMountStrategy",
"BlaxelSandboxClient",
"BlaxelSandboxClientOptions",
"BlaxelSandboxSession",
"BlaxelSandboxSessionState",
"BlaxelTimeouts",
"ExposedPortUnavailableError",
"InvalidManifestPathError",
"WorkspaceArchiveReadError",
},
),
(
"agents.extensions.sandbox.cloudflare",
{
"CloudflareBucketMountConfig",
"CloudflareBucketMountStrategy",
"CloudflareSandboxClient",
"CloudflareSandboxClientOptions",
"CloudflareSandboxSession",
"CloudflareSandboxSessionState",
},
),
(
"agents.extensions.sandbox.runloop",
{
"DEFAULT_RUNLOOP_WORKSPACE_ROOT",
"DEFAULT_RUNLOOP_ROOT_WORKSPACE_ROOT",
"RunloopAfterIdle",
"RunloopGatewaySpec",
"RunloopLaunchParameters",
"RunloopMcpSpec",
"RunloopPlatformAxonsClient",
"RunloopPlatformBenchmarksClient",
"RunloopPlatformBlueprintsClient",
"RunloopPlatformClient",
"RunloopPlatformNetworkPoliciesClient",
"RunloopPlatformSecretsClient",
"RunloopCloudBucketMountStrategy",
"RunloopSandboxClient",
"RunloopSandboxClientOptions",
"RunloopSandboxSession",
"RunloopSandboxSessionState",
"RunloopTimeouts",
"RunloopTunnelConfig",
"RunloopUserParameters",
"_decode_runloop_snapshot_ref",
"_encode_runloop_snapshot_ref",
},
),
(
"agents.extensions.sandbox.vercel",
{
"VercelSandboxClient",
"VercelSandboxClientOptions",
"VercelSandboxSession",
"VercelSandboxSessionState",
},
),
],
)
def test_extension_sandbox_package_export_surfaces_are_stable(
module_name: str,
expected_exports: set[str],
) -> None:
module = pytest.importorskip(module_name)
assert set(module.__all__) == expected_exports
for name in expected_exports:
assert getattr(module, name) is not None
def test_sandbox_dataclass_constructor_field_order_is_stable() -> None:
assert _dataclass_field_names(SandboxConcurrencyLimits) == (
"manifest_entries",
"local_dir_files",
)
assert _dataclass_field_names(SandboxArchiveLimits) == (
"max_input_bytes",
"max_extracted_bytes",
"max_members",
)
assert _dataclass_field_names(SandboxRunConfig) == (
"client",
"options",
"session",
"session_state",
"manifest",
"snapshot",
"concurrency_limits",
"archive_limits",
)
@pytest.mark.parametrize(
("module_name", "class_name", "expected_fields"),
[
(
"agents.extensions.sandbox.blaxel",
"BlaxelSandboxClientOptions",
(
"image",
"memory",
"region",
"ports",
"env_vars",
"labels",
"ttl",
"name",
"pause_on_exit",
"timeouts",
"exposed_port_public",
"exposed_port_url_ttl_s",
),
),
],
)
def test_optional_sandbox_dataclass_constructor_field_order_is_stable(
module_name: str,
class_name: str,
expected_fields: tuple[str, ...],
) -> None:
cls = _import_optional_class(module_name, class_name)
assert _dataclass_field_names(cls) == expected_fields
@pytest.mark.parametrize(
("module_name", "class_name", "expected_fields"),
[
(
"agents.sandbox.sandboxes.unix_local",
"UnixLocalSandboxClientOptions",
("exposed_ports",),
),
(
"agents.sandbox.sandboxes.docker",
"DockerSandboxClientOptions",
("image", "exposed_ports"),
),
(
"agents.extensions.sandbox.e2b",
"E2BSandboxClientOptions",
(
"sandbox_type",
"template",
"timeout",
"metadata",
"envs",
"secure",
"allow_internet_access",
"timeouts",
"pause_on_exit",
"exposed_ports",
"workspace_persistence",
"on_timeout",
"auto_resume",
"mcp",
),
),
(
"agents.extensions.sandbox.modal",
"ModalSandboxClientOptions",
(
"app_name",
"sandbox_create_timeout_s",
"workspace_persistence",
"snapshot_filesystem_timeout_s",
"snapshot_filesystem_restore_timeout_s",
"exposed_ports",
"gpu",
"timeout",
"use_sleep_cmd",
"image_builder_version",
"idle_timeout",
),
),
(
"agents.extensions.sandbox.cloudflare",
"CloudflareSandboxClientOptions",
("worker_url", "api_key", "exposed_ports"),
),
(
"agents.extensions.sandbox.daytona",
"DaytonaSandboxClientOptions",
(
"sandbox_snapshot_name",
"image",
"resources",
"env_vars",
"pause_on_exit",
"create_timeout",
"start_timeout",
"name",
"auto_stop_interval",
"timeouts",
"exposed_ports",
"exposed_port_url_ttl_s",
),
),
(
"agents.extensions.sandbox.runloop",
"RunloopSandboxClientOptions",
(
"blueprint_id",
"blueprint_name",
"env_vars",
"pause_on_exit",
"name",
"timeouts",
"exposed_ports",
"user_parameters",
"launch_parameters",
"tunnel",
"gateways",
"mcp",
"metadata",
"managed_secrets",
),
),
(
"agents.extensions.sandbox.vercel",
"VercelSandboxClientOptions",
(
"project_id",
"team_id",
"timeout_ms",
"runtime",
"resources",
"env",
"exposed_ports",
"interactive",
"workspace_persistence",
"snapshot_expiration_ms",
"network_policy",
),
),
],
)
def test_optional_sandbox_client_options_positional_field_order_is_stable(
module_name: str,
class_name: str,
expected_fields: tuple[str, ...],
) -> None:
options_cls = _import_optional_class(module_name, class_name)
assert _model_field_names(options_cls, exclude={"type"}) == expected_fields
@pytest.mark.parametrize(
("state_cls_or_module", "class_name", "expected_fields"),
[
(
SandboxSessionState,
None,
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
),
),
(
"agents.sandbox.sandboxes.unix_local",
"UnixLocalSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"workspace_root_owned",
),
),
(
"agents.sandbox.sandboxes.docker",
"DockerSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"image",
"container_id",
),
),
(
"agents.extensions.sandbox.e2b",
"E2BSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"sandbox_id",
"sandbox_type",
"template",
"sandbox_timeout",
"metadata",
"base_envs",
"secure",
"allow_internet_access",
"timeouts",
"pause_on_exit",
"workspace_persistence",
"on_timeout",
"auto_resume",
"mcp",
),
),
(
"agents.extensions.sandbox.modal",
"ModalSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"app_name",
"image_id",
"image_tag",
"sandbox_create_timeout_s",
"sandbox_id",
"workspace_persistence",
"snapshot_filesystem_timeout_s",
"snapshot_filesystem_restore_timeout_s",
"gpu",
"timeout",
"use_sleep_cmd",
"image_builder_version",
"idle_timeout",
),
),
(
"agents.extensions.sandbox.cloudflare",
"CloudflareSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"worker_url",
"sandbox_id",
),
),
(
"agents.extensions.sandbox.daytona",
"DaytonaSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"sandbox_id",
"sandbox_snapshot_name",
"image",
"base_env_vars",
"pause_on_exit",
"create_timeout",
"start_timeout",
"name",
"resources",
"auto_stop_interval",
"timeouts",
"exposed_port_url_ttl_s",
),
),
(
"agents.extensions.sandbox.blaxel",
"BlaxelSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"sandbox_name",
"image",
"memory",
"region",
"base_env_vars",
"labels",
"ttl",
"pause_on_exit",
"timeouts",
"sandbox_url",
"exposed_port_public",
"exposed_port_url_ttl_s",
),
),
(
"agents.extensions.sandbox.runloop",
"RunloopSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"devbox_id",
"blueprint_id",
"blueprint_name",
"base_env_vars",
"pause_on_exit",
"name",
"timeouts",
"user_parameters",
"launch_parameters",
"tunnel",
"gateways",
"mcp",
"metadata",
"secret_refs",
),
),
(
"agents.extensions.sandbox.vercel",
"VercelSandboxSessionState",
(
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
"sandbox_id",
"project_id",
"team_id",
"timeout_ms",
"runtime",
"resources",
"env",
"interactive",
"workspace_persistence",
"snapshot_expiration_ms",
"network_policy",
),
),
],
)
def test_sandbox_session_state_field_order_is_stable(
state_cls_or_module: type[SandboxSessionState] | str,
class_name: str | None,
expected_fields: tuple[str, ...],
) -> None:
if isinstance(state_cls_or_module, str):
assert class_name is not None
state_cls = _import_optional_class(state_cls_or_module, class_name)
else:
state_cls = state_cls_or_module
assert _model_field_names(state_cls) == expected_fields
@pytest.mark.parametrize(
("module_name", "class_name", "args", "expected_type"),
[
(
"agents.sandbox.sandboxes.unix_local",
"UnixLocalSandboxClientOptions",
(),
"unix_local",
),
(
"agents.sandbox.sandboxes.docker",
"DockerSandboxClientOptions",
("python:3.12",),
"docker",
),
("agents.extensions.sandbox.e2b", "E2BSandboxClientOptions", ("base",), "e2b"),
("agents.extensions.sandbox.modal", "ModalSandboxClientOptions", ("agents-sdk",), "modal"),
(
"agents.extensions.sandbox.cloudflare",
"CloudflareSandboxClientOptions",
("https://worker.example",),
"cloudflare",
),
("agents.extensions.sandbox.daytona", "DaytonaSandboxClientOptions", (), "daytona"),
("agents.extensions.sandbox.runloop", "RunloopSandboxClientOptions", (), "runloop"),
("agents.extensions.sandbox.vercel", "VercelSandboxClientOptions", (), "vercel"),
],
)
def test_optional_sandbox_client_options_json_round_trip_preserves_type(
module_name: str,
class_name: str,
args: tuple[object, ...],
expected_type: str,
) -> None:
options = cast(
BaseSandboxClientOptions,
_instantiate_optional_class(module_name, class_name, *args),
)
payload = options.model_dump(mode="json")
restored = BaseSandboxClientOptions.parse(payload)
assert payload["type"] == expected_type
assert _class_identity(restored) == _class_identity(options)
assert restored.model_dump(mode="json") == payload
@pytest.mark.parametrize(
("module_name", "class_name", "overrides"),
[
(
"agents.sandbox.sandboxes.unix_local",
"UnixLocalSandboxSessionState",
{"workspace_root_owned": True},
),
(
"agents.sandbox.sandboxes.docker",
"DockerSandboxSessionState",
{"image": "python:3.12", "container_id": "container-123"},
),
("agents.extensions.sandbox.e2b", "E2BSandboxSessionState", {"sandbox_id": "sandbox-123"}),
(
"agents.extensions.sandbox.modal",
"ModalSandboxSessionState",
{"app_name": "agents-sdk", "sandbox_id": "sandbox-123"},
),
(
"agents.extensions.sandbox.cloudflare",
"CloudflareSandboxSessionState",
{"worker_url": "https://worker.example", "sandbox_id": "sandbox-123"},
),
(
"agents.extensions.sandbox.daytona",
"DaytonaSandboxSessionState",
{"sandbox_id": "sandbox-123"},
),
(
"agents.extensions.sandbox.blaxel",
"BlaxelSandboxSessionState",
{"sandbox_name": "sandbox-123"},
),
(
"agents.extensions.sandbox.runloop",
"RunloopSandboxSessionState",
{"devbox_id": "devbox-123"},
),
(
"agents.extensions.sandbox.vercel",
"VercelSandboxSessionState",
{"sandbox_id": "sandbox-123"},
),
],
)
def test_optional_sandbox_session_state_json_round_trip_preserves_type(
module_name: str,
class_name: str,
overrides: dict[str, object],
) -> None:
state = _make_optional_session_state(module_name, class_name, **overrides)
payload = state.model_dump(mode="json")
restored = SandboxSessionState.parse(payload)
assert _class_identity(restored) == _class_identity(state)
assert restored.model_dump(mode="json") == payload
def test_core_discriminator_type_strings_are_stable() -> None:
expected_types = {
LocalSnapshot: "local",
NoopSnapshot: "noop",
RemoteSnapshot: "remote",
Dir: "dir",
File: "file",
LocalFile: "local_file",
LocalDir: "local_dir",
GitRepo: "git_repo",
S3Mount: "s3_mount",
R2Mount: "r2_mount",
GCSMount: "gcs_mount",
AzureBlobMount: "azure_blob_mount",
S3FilesMount: "s3_files_mount",
FuseMountPattern: "fuse",
MountpointMountPattern: "mountpoint",
RcloneMountPattern: "rclone",
S3FilesMountPattern: "s3files",
InContainerMountStrategy: "in_container",
DockerVolumeMountStrategy: "docker_volume",
}
for cls, expected_type in expected_types.items():
assert _model_type_default(cls) == expected_type
@pytest.mark.parametrize(
("module_name", "class_name", "expected_type"),
[
("agents.sandbox.sandboxes.unix_local", "UnixLocalSandboxClientOptions", "unix_local"),
("agents.sandbox.sandboxes.unix_local", "UnixLocalSandboxSessionState", "unix_local"),
("agents.sandbox.sandboxes.docker", "DockerSandboxClientOptions", "docker"),
("agents.sandbox.sandboxes.docker", "DockerSandboxSessionState", "docker"),
],
)
def test_optional_sandbox_discriminator_type_strings_are_stable(
module_name: str,
class_name: str,
expected_type: str,
) -> None:
cls = _import_optional_class(module_name, class_name)
assert _model_type_default(cls) == expected_type
@pytest.mark.parametrize(
("strategy", "expected_type"),
[
(InContainerMountStrategy(pattern=MountpointMountPattern()), "in_container"),
(DockerVolumeMountStrategy(driver="rclone"), "docker_volume"),
],
)
def test_mount_strategy_type_strings_round_trip_through_registry(
strategy: MountStrategyBase,
expected_type: str,
) -> None:
payload = strategy.model_dump(mode="json")
restored = MountStrategyBase.parse(payload)
assert payload["type"] == expected_type
assert _class_identity(restored) == _class_identity(strategy)
assert restored.model_dump(mode="json") == payload
@pytest.mark.parametrize(
("module_name", "class_name", "expected_type"),
[
("agents.extensions.sandbox.e2b", "E2BCloudBucketMountStrategy", "e2b_cloud_bucket"),
("agents.extensions.sandbox.modal", "ModalCloudBucketMountStrategy", "modal_cloud_bucket"),
(
"agents.extensions.sandbox.daytona",
"DaytonaCloudBucketMountStrategy",
"daytona_cloud_bucket",
),
(
"agents.extensions.sandbox.cloudflare",
"CloudflareBucketMountStrategy",
"cloudflare_bucket_mount",
),
(
"agents.extensions.sandbox.blaxel",
"BlaxelCloudBucketMountStrategy",
"blaxel_cloud_bucket",
),
("agents.extensions.sandbox.blaxel", "BlaxelDriveMountStrategy", "blaxel_drive"),
(
"agents.extensions.sandbox.runloop",
"RunloopCloudBucketMountStrategy",
"runloop_cloud_bucket",
),
],
)
def test_optional_mount_strategy_type_strings_round_trip_through_registry(
module_name: str,
class_name: str,
expected_type: str,
) -> None:
strategy = cast(
MountStrategyBase,
_instantiate_optional_class(module_name, class_name),
)
payload = strategy.model_dump(mode="json")
restored = MountStrategyBase.parse(payload)
assert payload["type"] == expected_type
assert _class_identity(restored) == _class_identity(strategy)
assert restored.model_dump(mode="json") == payload
def test_core_discriminator_registries_parse_released_payload_shapes() -> None:
assert isinstance(SnapshotBase.parse({"type": "noop", "id": "snapshot-123"}), NoopSnapshot)
assert isinstance(
BaseEntry.parse({"type": "dir", "permissions": {"directory": True}}),
Dir,
)
assert isinstance(
TypeAdapter(MountPattern).validate_python({"type": "mountpoint"}),
MountpointMountPattern,
)
assert isinstance(
MountStrategyBase.parse({"type": "docker_volume", "driver": "rclone"}),
DockerVolumeMountStrategy,
)
@pytest.mark.asyncio
async def test_run_state_sandbox_payload_json_shape_is_stable() -> None:
agent = Agent(name="sandbox", instructions="Use the sandbox.")
session_state = TestSessionState(
session_id=uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
snapshot=NoopSnapshot(id="snapshot-123"),
manifest=Manifest(root="/workspace"),
exposed_ports=(8000,),
workspace_root_ready=True,
).model_dump(mode="json")
sandbox_payload = {
"backend_id": "fake",
"current_agent_key": "sandbox",
"current_agent_name": "sandbox",
"session_state": session_state,
"sessions_by_agent": {
"sandbox": {
"agent_name": "sandbox",
"session_state": session_state,
},
},
}
state: RunState[dict[str, Any], Agent[Any]] = RunState(
context=RunContextWrapper(context={}),
original_input="hello",
starting_agent=agent,
)
state._sandbox = sandbox_payload
state_json = state.to_json()
restored = await RunState.from_json(agent, state_json)
assert state_json["sandbox"] == sandbox_payload
assert tuple(state_json["sandbox"]) == (
"backend_id",
"current_agent_key",
"current_agent_name",
"session_state",
"sessions_by_agent",
)
assert tuple(state_json["sandbox"]["session_state"]) == (
"type",
"session_id",
"snapshot",
"manifest",
"exposed_ports",
"snapshot_fingerprint",
"snapshot_fingerprint_version",
"workspace_root_ready",
)
assert restored._sandbox == sandbox_payload
def _dataclass_field_names(cls: type[Any]) -> tuple[str, ...]:
return tuple(field.name for field in dataclasses.fields(cls) if field.init)
def _model_field_names(
cls: type[Any],
*,
exclude: Iterable[str] = (),
) -> tuple[str, ...]:
excluded = set(exclude)
return tuple(name for name in cls.model_fields if name not in excluded)
def _model_type_default(cls: type[Any]) -> str:
type_field = cls.model_fields["type"]
assert isinstance(type_field.default, str)
return type_field.default
def _class_identity(value: object) -> tuple[str, str]:
value_type = type(value)
return value_type.__module__, value_type.__qualname__