agentenv-framework 0.9.1254__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_env/__init__.py +0 -0
- agent_env/a2a_agent/__init__.py +23 -0
- agent_env/a2a_agent/a2a_agent.py +600 -0
- agent_env/a2a_agent/conversation_store.py +356 -0
- agent_env/a2a_agent/object_transfer.py +609 -0
- agent_env/a2a_agent/protocol.py +238 -0
- agent_env/a2a_agent/store.py +201 -0
- agent_env/a2a_agent/validator.py +901 -0
- agent_env/artifact/__init__.py +48 -0
- agent_env/artifact/artifact.py +74 -0
- agent_env/artifact/artifacts/__init__.py +0 -0
- agent_env/artifact/artifacts/cli.py +83 -0
- agent_env/artifact/artifacts/docker_image.py +457 -0
- agent_env/artifact/artifacts/environment.py +89 -0
- agent_env/artifact/artifacts/environment_universe.py +156 -0
- agent_env/artifact/artifacts/file.py +214 -0
- agent_env/artifact/artifacts/file_artifact_universe.py +206 -0
- agent_env/artifact/artifacts/skill.py +206 -0
- agent_env/artifact/artifacts/vm_image.py +95 -0
- agent_env/artifact/ref.py +15 -0
- agent_env/artifact/registry.py +251 -0
- agent_env/artifact/store.py +295 -0
- agent_env/artifact/universe.py +38 -0
- agent_env/attribution.py +41 -0
- agent_env/bundle/__init__.py +4 -0
- agent_env/bundle/_fs.py +128 -0
- agent_env/bundle/authoring.py +239 -0
- agent_env/bundle/installed.py +175 -0
- agent_env/bundle/ledger.py +269 -0
- agent_env/bundle/materialize.py +204 -0
- agent_env/bundle/parse.py +594 -0
- agent_env/bundle/plan.py +414 -0
- agent_env/bundle/resolve.py +404 -0
- agent_env/bundle/run.py +361 -0
- agent_env/cli/__init__.py +69 -0
- agent_env/cli/__main__.py +10 -0
- agent_env/cli/_installers.py +576 -0
- agent_env/cli/_plugin_changes.py +947 -0
- agent_env/cli/a2a_agent/__init__.py +22 -0
- agent_env/cli/a2a_agent/add_skill.py +97 -0
- agent_env/cli/a2a_agent/deploy.py +60 -0
- agent_env/cli/a2a_agent/get.py +15 -0
- agent_env/cli/a2a_agent/get_instance.py +26 -0
- agent_env/cli/a2a_agent/put.py +78 -0
- agent_env/cli/a2a_agent/validate.py +56 -0
- agent_env/cli/artifact/__init__.py +20 -0
- agent_env/cli/artifact/cli.py +83 -0
- agent_env/cli/artifact/environment.py +44 -0
- agent_env/cli/artifact/environment_universe.py +163 -0
- agent_env/cli/artifact/file_artifact_universe.py +281 -0
- agent_env/cli/artifact/skill.py +128 -0
- agent_env/cli/banner.py +25 -0
- agent_env/cli/config.py +263 -0
- agent_env/cli/env/__init__.py +31 -0
- agent_env/cli/env/deploy.py +112 -0
- agent_env/cli/env/gateway.py +57 -0
- agent_env/cli/env/get_instance.py +40 -0
- agent_env/cli/env/mcp_server.py +410 -0
- agent_env/cli/env/multi.py +282 -0
- agent_env/cli/env/service_db.py +99 -0
- agent_env/cli/env/snapshot.py +35 -0
- agent_env/cli/env/state/__init__.py +6 -0
- agent_env/cli/env/state/init.py +108 -0
- agent_env/cli/env/state/teardown.py +44 -0
- agent_env/cli/env/website.py +216 -0
- agent_env/cli/env/website_browser.py +67 -0
- agent_env/cli/eval/__init__.py +16 -0
- agent_env/cli/eval/add_tasks.py +63 -0
- agent_env/cli/eval/create.py +49 -0
- agent_env/cli/eval/run.py +194 -0
- agent_env/cli/identity/__init__.py +5 -0
- agent_env/cli/identity/client_identity.py +37 -0
- agent_env/cli/plugin.py +438 -0
- agent_env/cli/run.py +285 -0
- agent_env/cli/task/__init__.py +21 -0
- agent_env/cli/task/create.py +109 -0
- agent_env/cli/task/get.py +17 -0
- agent_env/cli/task/get_instance.py +34 -0
- agent_env/cli/task/run.py +615 -0
- agent_env/cli/task/validate.py +31 -0
- agent_env/cli/up.py +123 -0
- agent_env/cli/utils.py +183 -0
- agent_env/config/__init__.py +50 -0
- agent_env/config/describe.py +1067 -0
- agent_env/config/errors.py +13 -0
- agent_env/config/loader.py +245 -0
- agent_env/config/model.py +154 -0
- agent_env/config/paths.py +25 -0
- agent_env/config/plugin_tables.py +101 -0
- agent_env/config/provenance.py +134 -0
- agent_env/config/runtime.py +1064 -0
- agent_env/config/snapshot.py +81 -0
- agent_env/entity_refs.py +186 -0
- agent_env/env/__init__.py +49 -0
- agent_env/env/env.py +433 -0
- agent_env/env/env_artifact_store.py +87 -0
- agent_env/env/envs/__init__.py +7 -0
- agent_env/env/envs/_deployment.py +281 -0
- agent_env/env/envs/gateway_server.py +32 -0
- agent_env/env/envs/mcp_server.py +387 -0
- agent_env/env/envs/multi_env.py +780 -0
- agent_env/env/envs/service_db/Dockerfile +20 -0
- agent_env/env/envs/service_db/Dockerfile.db-mcp +42 -0
- agent_env/env/envs/service_db/Dockerfile.db-web +8 -0
- agent_env/env/envs/service_db.py +92 -0
- agent_env/env/envs/website.py +294 -0
- agent_env/env/envs/website_browser/Dockerfile +40 -0
- agent_env/env/envs/website_browser/__init__.py +17 -0
- agent_env/env/envs/website_browser/entrypoint.sh +13 -0
- agent_env/env/gateway/Dockerfile +14 -0
- agent_env/env/gateway/__init__.py +37 -0
- agent_env/env/gateway/clock.py +184 -0
- agent_env/env/gateway/constants.py +190 -0
- agent_env/env/gateway/entrypoint.py +156 -0
- agent_env/env/gateway/gateway.py +1095 -0
- agent_env/env/gateway/get_time.py +60 -0
- agent_env/env/gateway/requirements.txt +4 -0
- agent_env/env/gateway/triggers.py +1193 -0
- agent_env/env/legacy_protocol.py +146 -0
- agent_env/env/registry.py +93 -0
- agent_env/env/snapshot_store.py +372 -0
- agent_env/env/store.py +411 -0
- agent_env/eval/__init__.py +14 -0
- agent_env/eval/eval.py +68 -0
- agent_env/eval/store.py +116 -0
- agent_env/examples/hello/README.md +9 -0
- agent_env/examples/hello/artifacts/greeting/check.sh +1 -0
- agent_env/examples/hello/artifacts/greeting/hello.txt +1 -0
- agent_env/examples/hello/tasks/hello.json +10 -0
- agent_env/explorer/__init__.py +12 -0
- agent_env/explorer/app.py +277 -0
- agent_env/explorer/openapi_docs.py +242 -0
- agent_env/explorer/plugin.py +95 -0
- agent_env/explorer/routers/__init__.py +1 -0
- agent_env/explorer/routers/common.py +174 -0
- agent_env/explorer/routers/conversations.py +25 -0
- agent_env/explorer/routers/objects.py +112 -0
- agent_env/explorer/routers/runs.py +475 -0
- agent_env/plugins/__init__.py +59 -0
- agent_env/plugins/_cli.py +244 -0
- agent_env/plugins/_discovery.py +97 -0
- agent_env/plugins/_inventory.py +262 -0
- agent_env/plugins/_registration.py +268 -0
- agent_env/plugins/_report.py +47 -0
- agent_env/plugins/_requirements.py +55 -0
- agent_env/providers/__init__.py +37 -0
- agent_env/providers/env_providers/__init__.py +12 -0
- agent_env/providers/env_providers/constants.py +13 -0
- agent_env/providers/env_providers/env_gateway_provider.py +1354 -0
- agent_env/providers/env_providers/env_provider.py +237 -0
- agent_env/providers/env_providers/env_server_provider.py +133 -0
- agent_env/providers/env_state/__init__.py +49 -0
- agent_env/providers/env_state/env_state_provider.py +554 -0
- agent_env/providers/env_state/local_postgres.py +390 -0
- agent_env/providers/env_state/store.py +112 -0
- agent_env/providers/sandbox_providers/__init__.py +29 -0
- agent_env/providers/sandbox_providers/chained_sandbox_provider.py +93 -0
- agent_env/providers/sandbox_providers/e2b/__init__.py +6 -0
- agent_env/providers/sandbox_providers/e2b/provider.py +376 -0
- agent_env/providers/sandbox_providers/e2b/sandbox.py +342 -0
- agent_env/providers/sandbox_providers/e2b/template.py +158 -0
- agent_env/providers/sandbox_providers/local_sandbox.py +408 -0
- agent_env/providers/sandbox_providers/modal_sandbox.py +500 -0
- agent_env/providers/sandbox_providers/modal_vm_sandbox.py +466 -0
- agent_env/providers/sandbox_providers/sandbox.py +387 -0
- agent_env/providers/sandbox_providers/sandbox_provider.py +614 -0
- agent_env/py.typed +0 -0
- agent_env/runner/__init__.py +16 -0
- agent_env/runner/local_runner.py +190 -0
- agent_env/runner/runner.py +127 -0
- agent_env/runner/store.py +94 -0
- agent_env/store/__init__.py +126 -0
- agent_env/store/_google.py +68 -0
- agent_env/store/base.py +33 -0
- agent_env/store/document_store/__init__.py +60 -0
- agent_env/store/document_store/document_store.py +519 -0
- agent_env/store/document_store/dynamodb_document_store.py +306 -0
- agent_env/store/document_store/evaluation.py +199 -0
- agent_env/store/document_store/firestore_mongo_document_store.py +242 -0
- agent_env/store/document_store/mongo_document_store.py +259 -0
- agent_env/store/document_store/sqlite_document_store.py +314 -0
- agent_env/store/ids.py +123 -0
- agent_env/store/image_store/__init__.py +34 -0
- agent_env/store/image_store/ecr_image_store.py +131 -0
- agent_env/store/image_store/google_credentials.py +113 -0
- agent_env/store/image_store/image_store.py +113 -0
- agent_env/store/image_store/local_registry_image_store.py +106 -0
- agent_env/store/image_store/oci_registry_credentials.py +196 -0
- agent_env/store/local_state.py +27 -0
- agent_env/store/object_store/__init__.py +24 -0
- agent_env/store/object_store/gcs_object_store.py +415 -0
- agent_env/store/object_store/local_object_store.py +143 -0
- agent_env/store/object_store/object_store.py +198 -0
- agent_env/store/object_store/s3_object_store.py +354 -0
- agent_env/store/query.py +112 -0
- agent_env/store/routing.py +609 -0
- agent_env/store/secret_store/__init__.py +19 -0
- agent_env/store/secret_store/aws_secrets_manager_secret_store.py +293 -0
- agent_env/store/secret_store/gcp_secret_manager_secret_store.py +319 -0
- agent_env/store/secret_store/local_secret_store.py +47 -0
- agent_env/store/secret_store/secret_store.py +19 -0
- agent_env/task/__init__.py +27 -0
- agent_env/task/interrupts.py +109 -0
- agent_env/task/registry.py +27 -0
- agent_env/task/step_journal.py +99 -0
- agent_env/task/store.py +940 -0
- agent_env/task/task.py +815 -0
- agent_env/task/teardown.py +142 -0
- agent_env/task_step/__init__.py +47 -0
- agent_env/task_step/context.py +195 -0
- agent_env/task_step/context_ops.py +224 -0
- agent_env/task_step/registry.py +182 -0
- agent_env/task_step/review_store.py +118 -0
- agent_env/task_step/snapshot_utils/__init__.py +2 -0
- agent_env/task_step/snapshot_utils/agent_state_capture.py +261 -0
- agent_env/task_step/snapshot_utils/snapshot_series.py +631 -0
- agent_env/task_step/store.py +129 -0
- agent_env/task_step/task_step.py +173 -0
- agent_env/task_step/task_steps/__init__.py +27 -0
- agent_env/task_step/task_steps/a2a_agent_validator/__init__.py +31 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/README.md +158 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.m4a +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp3 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp4 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.ogg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.wav +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/document.pdf +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.gif +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.jpg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.png +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_card.py +103 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_config_identity.py +69 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_mcp.py +178 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_core_protocol.py +67 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_install.py +115 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution.py +124 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution_runtime.py +173 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_modalities.py +270 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_peer_agents.py +102 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_role.py +151 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_skill_config.py +194 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_snapshot.py +111 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_system_prompt.py +215 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_trajectory.py +164 -0
- agent_env/task_step/task_steps/add_skills.py +240 -0
- agent_env/task_step/task_steps/apply_server_config.py +288 -0
- agent_env/task_step/task_steps/collect_artifacts.py +940 -0
- agent_env/task_step/task_steps/deploy_agent.py +626 -0
- agent_env/task_step/task_steps/deploy_env.py +197 -0
- agent_env/task_step/task_steps/deploy_human_agent.py +100 -0
- agent_env/task_step/task_steps/deploy_sandbox.py +170 -0
- agent_env/task_step/task_steps/env_card_validator/__init__.py +4 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_card.py +109 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_core_protocol.py +108 -0
- agent_env/task_step/task_steps/install_agent.py +376 -0
- agent_env/task_step/task_steps/load_artifact.py +775 -0
- agent_env/task_step/task_steps/mcp_cli_builder/__init__.py +4 -0
- agent_env/task_step/task_steps/mcp_cli_builder/build_mcp_cli.py +201 -0
- agent_env/task_step/task_steps/mcp_cli_builder/codegen.py +627 -0
- agent_env/task_step/task_steps/mcp_env_validator/__init__.py +44 -0
- agent_env/task_step/task_steps/mcp_env_validator/validation_gate_aggregator.py +102 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_env_assessment.py +78 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_tool_schema.py +152 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_spec_conformance.py +486 -0
- agent_env/task_step/task_steps/modify_env_tool_access.py +80 -0
- agent_env/task_step/task_steps/multienv_validator/__init__.py +3 -0
- agent_env/task_step/task_steps/multienv_validator/combine_universe_verdicts.py +107 -0
- agent_env/task_step/task_steps/multienv_validator/universe_comparison.py +253 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_agent_judge.py +180 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_roundtrip.py +241 -0
- agent_env/task_step/task_steps/peer_agents.py +92 -0
- agent_env/task_step/task_steps/prompt_agent.py +900 -0
- agent_env/task_step/task_steps/register_agent_triggers.py +113 -0
- agent_env/task_step/task_steps/register_env_triggers.py +97 -0
- agent_env/task_step/task_steps/reset_env.py +71 -0
- agent_env/task_step/task_steps/review.py +152 -0
- agent_env/task_step/task_steps/run_code.py +461 -0
- agent_env/task_step/task_steps/run_code_runner.py +52 -0
- agent_env/task_step/task_steps/run_docker_container.py +380 -0
- agent_env/task_step/task_steps/sandbox_utils/__init__.py +0 -0
- agent_env/task_step/task_steps/sandbox_utils/sandbox_utils.py +79 -0
- agent_env/task_step/task_steps/snapshot_agent_state.py +205 -0
- agent_env/task_step/task_steps/snapshot_env.py +637 -0
- agent_env/task_step/task_steps/sync_env_clock.py +132 -0
- agent_env/task_step/task_steps/teardown_sandboxes.py +170 -0
- agent_env/task_step/task_steps/verifiers/__init__.py +23 -0
- agent_env/task_step/task_steps/verifiers/agent_prompt_response_verifier.py +166 -0
- agent_env/task_step/task_steps/verifiers/aggregate_verifiers.py +90 -0
- agent_env/task_step/task_steps/verifiers/env_outcome_verifier.py +127 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/__init__.py +2 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/frame_selection.py +377 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/judge_output_format.py +1293 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/trajectory_filter.py +514 -0
- agent_env/task_step/task_steps/verifiers/rubrics_verifier.py +1318 -0
- agent_env/task_step/task_steps/verifiers/run_container_unit_tests_verifier.py +434 -0
- agent_env/task_step/task_steps/verifiers/scoring.py +40 -0
- agent_env/task_step/task_steps/verifiers/verify_sandbox.py +308 -0
- agent_env/task_step/thread_work.py +61 -0
- agent_env/utils/__init__.py +0 -0
- agent_env/utils/card_naming.py +108 -0
- agent_env/utils/deprecation.py +109 -0
- agent_env/utils/docker_build.py +29 -0
- agent_env/utils/exec_retry.py +77 -0
- agent_env/utils/litellm_attribution.py +59 -0
- agent_env/utils/paths.py +20 -0
- agentenv_framework-0.9.1254.dist-info/METADATA +394 -0
- agentenv_framework-0.9.1254.dist-info/RECORD +312 -0
- agentenv_framework-0.9.1254.dist-info/WHEEL +4 -0
- agentenv_framework-0.9.1254.dist-info/entry_points.txt +5 -0
- agentenv_framework-0.9.1254.dist-info/licenses/LICENSE +202 -0
- agentenv_framework-0.9.1254.dist-info/licenses/NOTICE +4 -0
- agentenv_framework-0.9.1254.dist-info/licenses/THIRD_PARTY_NOTICES.md +7665 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""E2B VM sandbox provider.
|
|
2
|
+
|
|
3
|
+
E2B templates are immutable once built. The configured ``base_template`` is
|
|
4
|
+
therefore the only template input accepted by this provider; the template
|
|
5
|
+
resolver derives a versioned resource-specific template for each CPU/memory
|
|
6
|
+
pair. This keeps an arbitrary caller-supplied image from bypassing the
|
|
7
|
+
reviewed base image.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from ipaddress import ip_network
|
|
15
|
+
from typing import Any, ClassVar, Self
|
|
16
|
+
|
|
17
|
+
from agent_env.attribution import Attribution
|
|
18
|
+
from agent_env.config.errors import ConfigError
|
|
19
|
+
from agent_env.providers.sandbox_providers.e2b.sandbox import E2B_ALL_TRAFFIC, E2BSandbox
|
|
20
|
+
from agent_env.providers.sandbox_providers.modal_sandbox import _build_cost_attribution_tags
|
|
21
|
+
from agent_env.providers.sandbox_providers.sandbox import NetworkMode, NetworkPolicy
|
|
22
|
+
from agent_env.providers.sandbox_providers.sandbox_provider import (
|
|
23
|
+
SANDBOX_MODE_VM,
|
|
24
|
+
SandboxProvider,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_EXPOSED_PORTS_METADATA_KEY = "agent_env_exposed_ports"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class E2BSandboxProvider(SandboxProvider):
|
|
33
|
+
"""Provision Docker-capable E2B sandboxes from a configured base template.
|
|
34
|
+
|
|
35
|
+
``api_key`` is deliberately supplied by resolved provider config rather
|
|
36
|
+
than read from the process environment. Configure it with a ``secret:``
|
|
37
|
+
interpolation in ``[sandbox.providers.e2b.config]``.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# AsyncSandbox.get_host(port) returns ``<port>-<sandbox-id>.e2b.app``.
|
|
41
|
+
# Workloads receive those public URLs, so an allowlisted sandbox must be
|
|
42
|
+
# able to reach the E2B ingress domain just as it can reach Modal's.
|
|
43
|
+
EGRESS_HOSTS: ClassVar[tuple[str, ...]] = ("*.e2b.app",)
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
api_key: str,
|
|
49
|
+
base_template: str,
|
|
50
|
+
template_resolver: Any | None = None,
|
|
51
|
+
sandbox_cls: Any | None = None,
|
|
52
|
+
):
|
|
53
|
+
if not isinstance(api_key, str) or not api_key.strip():
|
|
54
|
+
raise ValueError("E2B api_key must be a non-empty resolved secret")
|
|
55
|
+
if not isinstance(base_template, str) or not base_template.strip():
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"E2B base_template must be a non-empty immutable template version"
|
|
58
|
+
)
|
|
59
|
+
self._api_key = api_key
|
|
60
|
+
self._base_template = base_template.strip()
|
|
61
|
+
self._template_resolver = template_resolver
|
|
62
|
+
# Kept injectable so unit tests never instantiate an SDK client. The
|
|
63
|
+
# import stays lazy so consumers that do not select E2B do not initialize
|
|
64
|
+
# SDK state while loading the provider package.
|
|
65
|
+
self._sandbox_cls = sandbox_cls
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_config(cls, **config: Any) -> Self:
|
|
69
|
+
"""Construct from provider config with an actionable missing-template error."""
|
|
70
|
+
if not config.get("base_template"):
|
|
71
|
+
raise ConfigError(
|
|
72
|
+
"[sandbox.providers.e2b.config] requires a non-empty 'base_template'"
|
|
73
|
+
)
|
|
74
|
+
return cls(**config)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def supports_network_policy(cls, policy: NetworkPolicy) -> bool:
|
|
78
|
+
# E2B's SandboxNetworkOpts accepts an outbound allow-list (domains and
|
|
79
|
+
# CIDRs) at create time.
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def base_template(self) -> str:
|
|
84
|
+
"""The immutable, versioned base template selected in provider config."""
|
|
85
|
+
return self._base_template
|
|
86
|
+
|
|
87
|
+
def _get_sandbox_cls(self) -> Any:
|
|
88
|
+
if self._sandbox_cls is None:
|
|
89
|
+
from e2b import AsyncSandbox
|
|
90
|
+
|
|
91
|
+
self._sandbox_cls = AsyncSandbox
|
|
92
|
+
return self._sandbox_cls
|
|
93
|
+
|
|
94
|
+
async def _resolve_template(self, *, cpu: float, memory: int) -> str:
|
|
95
|
+
if self._template_resolver is None:
|
|
96
|
+
from agent_env.providers.sandbox_providers.e2b.template import E2BTemplateResolver
|
|
97
|
+
|
|
98
|
+
self._template_resolver = E2BTemplateResolver(api_key=self._api_key)
|
|
99
|
+
return await self._template_resolver.resolve(
|
|
100
|
+
self._base_template,
|
|
101
|
+
cpu=cpu,
|
|
102
|
+
memory_mb=memory,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
async def create_vm(
|
|
106
|
+
self,
|
|
107
|
+
*,
|
|
108
|
+
image: str | None = None,
|
|
109
|
+
boot_mode: str | None = None,
|
|
110
|
+
cpu: float = 1.0,
|
|
111
|
+
memory: int = 8192,
|
|
112
|
+
disk_size_gb: float = 10,
|
|
113
|
+
timeout: int = 3600 * 2,
|
|
114
|
+
exposed_ports: list[int] | None = None,
|
|
115
|
+
setup_for_gateway: bool = True,
|
|
116
|
+
attribution: Attribution | None = None,
|
|
117
|
+
priority: int | None = None,
|
|
118
|
+
network_policy: NetworkPolicy | None = None,
|
|
119
|
+
) -> E2BSandbox:
|
|
120
|
+
"""Create an E2B VM using its derived immutable template.
|
|
121
|
+
|
|
122
|
+
``boot_mode``, ``disk_size_gb`` and ``priority`` exist for
|
|
123
|
+
``SandboxProvider`` parity only: E2B's sandbox-create API has no
|
|
124
|
+
corresponding controls. In particular, disk size is never sent to
|
|
125
|
+
E2B. ``image`` is rejected rather than silently overriding the
|
|
126
|
+
configured base template.
|
|
127
|
+
"""
|
|
128
|
+
if image is not None:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
"E2B base_template is immutable and configured by the provider; "
|
|
131
|
+
"image overrides are unsupported"
|
|
132
|
+
)
|
|
133
|
+
del boot_mode, priority
|
|
134
|
+
# ``10`` is the interface default inherited from SandboxProvider.
|
|
135
|
+
# Avoid a noisy warning for every existing caller while making any
|
|
136
|
+
# meaningful disk request explicit: E2B fixes disk capacity in its
|
|
137
|
+
# template and does not accept a per-sandbox disk-size control.
|
|
138
|
+
if disk_size_gb != 10:
|
|
139
|
+
logger.warning(
|
|
140
|
+
"Ignoring disk_size_gb=%s for E2B sandbox: E2B disk capacity "
|
|
141
|
+
"is fixed by the selected template and cannot be configured per sandbox",
|
|
142
|
+
disk_size_gb,
|
|
143
|
+
)
|
|
144
|
+
effective_policy = self.effective_network_policy(network_policy)
|
|
145
|
+
template = await self._resolve_template(cpu=cpu, memory=memory)
|
|
146
|
+
metadata = _build_cost_attribution_tags(dict(attribution or {}))
|
|
147
|
+
ports = list(exposed_ports or [])
|
|
148
|
+
if ports:
|
|
149
|
+
# E2B can derive a URL for any port, so the adapter's cache is the
|
|
150
|
+
# capability boundary. Persist the ports that agent-env actually
|
|
151
|
+
# exposed so reconnect can restore that boundary.
|
|
152
|
+
metadata[_EXPOSED_PORTS_METADATA_KEY] = ",".join(
|
|
153
|
+
str(port) for port in ports
|
|
154
|
+
)
|
|
155
|
+
logger.info(
|
|
156
|
+
"Creating E2B sandbox (template=%s, ports=%s, cpu=%s, memory=%sMB, "
|
|
157
|
+
"timeout=%ss, metadata=%s)",
|
|
158
|
+
template,
|
|
159
|
+
ports,
|
|
160
|
+
cpu,
|
|
161
|
+
memory,
|
|
162
|
+
timeout,
|
|
163
|
+
metadata,
|
|
164
|
+
)
|
|
165
|
+
create_kwargs: dict[str, Any] = {
|
|
166
|
+
"template": template,
|
|
167
|
+
"timeout": timeout,
|
|
168
|
+
"api_key": self._api_key,
|
|
169
|
+
"metadata": metadata,
|
|
170
|
+
# Public endpoints are the v1 behavior. E2B's authenticated
|
|
171
|
+
# traffic-token flow is intentionally deferred.
|
|
172
|
+
"network": {"allow_public_traffic": True},
|
|
173
|
+
}
|
|
174
|
+
if effective_policy.mode is NetworkMode.ALLOWLIST:
|
|
175
|
+
create_kwargs["network"]["allow_out"] = [
|
|
176
|
+
*effective_policy.allow_hosts,
|
|
177
|
+
*effective_policy.allow_cidrs,
|
|
178
|
+
]
|
|
179
|
+
create_kwargs["network"]["deny_out"] = [E2B_ALL_TRAFFIC]
|
|
180
|
+
raw_sandbox = await self._get_sandbox_cls().create(**create_kwargs)
|
|
181
|
+
sandbox: E2BSandbox | None = None
|
|
182
|
+
try:
|
|
183
|
+
sandbox = E2BSandbox(
|
|
184
|
+
raw_sandbox,
|
|
185
|
+
exposed_ports=ports,
|
|
186
|
+
network_policy=effective_policy,
|
|
187
|
+
)
|
|
188
|
+
# Make the mode explicit even if an adapter implementation changes its
|
|
189
|
+
# constructor default; gateway dispatch relies on this exact value.
|
|
190
|
+
sandbox.mode = SANDBOX_MODE_VM
|
|
191
|
+
if setup_for_gateway:
|
|
192
|
+
await sandbox.setup_vm_for_gateway(ports)
|
|
193
|
+
return sandbox
|
|
194
|
+
except BaseException:
|
|
195
|
+
try:
|
|
196
|
+
if sandbox is not None:
|
|
197
|
+
await sandbox.terminate()
|
|
198
|
+
else:
|
|
199
|
+
await raw_sandbox.kill()
|
|
200
|
+
except Exception as cleanup_error: # noqa: BLE001 - cleanup must not mask create failure
|
|
201
|
+
logger.warning(
|
|
202
|
+
"Failed to terminate E2B sandbox %s after setup failure: %s",
|
|
203
|
+
raw_sandbox.sandbox_id,
|
|
204
|
+
cleanup_error,
|
|
205
|
+
)
|
|
206
|
+
raise
|
|
207
|
+
|
|
208
|
+
async def create_sandbox(
|
|
209
|
+
self,
|
|
210
|
+
*,
|
|
211
|
+
image_name: str,
|
|
212
|
+
port: int,
|
|
213
|
+
env: dict[str, str],
|
|
214
|
+
cpu: float = 1.0,
|
|
215
|
+
memory: int = 8192,
|
|
216
|
+
disk_size_gb: float = 10,
|
|
217
|
+
timeout: int = 3600 * 2,
|
|
218
|
+
attribution: Attribution | None = None,
|
|
219
|
+
priority: int | None = None,
|
|
220
|
+
network_policy: NetworkPolicy | None = None,
|
|
221
|
+
) -> E2BSandbox:
|
|
222
|
+
# Like the other remote VM backends, E2B is a VM backend. The gateway/agent
|
|
223
|
+
# path loads and starts image_name after receiving the VM, so image_name
|
|
224
|
+
# and env are intentionally not SDK create parameters here.
|
|
225
|
+
del image_name, env
|
|
226
|
+
return await self.create_vm(
|
|
227
|
+
cpu=cpu,
|
|
228
|
+
memory=memory,
|
|
229
|
+
disk_size_gb=disk_size_gb,
|
|
230
|
+
timeout=timeout,
|
|
231
|
+
exposed_ports=[port],
|
|
232
|
+
attribution=attribution,
|
|
233
|
+
priority=priority,
|
|
234
|
+
network_policy=network_policy,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _info_field(value: Any, name: str, default: Any) -> Any:
|
|
239
|
+
"""Read an E2B SDK-model field, accepting its JSON spelling in tests."""
|
|
240
|
+
json_name = "".join(
|
|
241
|
+
part.capitalize() if index else part
|
|
242
|
+
for index, part in enumerate(name.split("_"))
|
|
243
|
+
)
|
|
244
|
+
if isinstance(value, dict):
|
|
245
|
+
return value.get(name, value.get(json_name, default))
|
|
246
|
+
return getattr(value, name, default)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def _is_sdk_unset(value: Any) -> bool:
|
|
250
|
+
# ``e2b.api.client.types.UNSET`` intentionally stays out of the module
|
|
251
|
+
# imports so this provider's SDK import remains lazy.
|
|
252
|
+
return type(value).__name__ == "Unset"
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def _network_policy_from_info(cls, info: Any, *, sandbox_id: str) -> NetworkPolicy:
|
|
256
|
+
"""Translate E2B's current egress config into agent-env's contract.
|
|
257
|
+
|
|
258
|
+
E2B returns the applied network configuration from ``get_info``. Do
|
|
259
|
+
not infer an unrestricted policy when that response contains a policy
|
|
260
|
+
this adapter cannot represent: image loading may extend a restrictive
|
|
261
|
+
policy with object-store hosts, and extending the wrong one is a
|
|
262
|
+
security bug.
|
|
263
|
+
"""
|
|
264
|
+
missing = object()
|
|
265
|
+
network = cls._info_field(info, "network", missing)
|
|
266
|
+
if network is missing or cls._is_sdk_unset(network) or network is None:
|
|
267
|
+
raise RuntimeError(
|
|
268
|
+
f"Cannot recover E2B network policy for sandbox {sandbox_id}: "
|
|
269
|
+
"get_info returned no network configuration"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
allow_internet = cls._info_field(info, "allow_internet_access", missing)
|
|
273
|
+
deny_out = cls._info_field(network, "deny_out", missing)
|
|
274
|
+
deny_out_missing = deny_out is missing or cls._is_sdk_unset(deny_out)
|
|
275
|
+
if allow_internet is False:
|
|
276
|
+
raise RuntimeError(
|
|
277
|
+
f"Cannot recover E2B network policy for sandbox {sandbox_id}: "
|
|
278
|
+
"the applied deny/allow-internet policy is not representable by agent-env"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if not deny_out_missing and deny_out not in (None, [], [E2B_ALL_TRAFFIC]):
|
|
282
|
+
raise RuntimeError(
|
|
283
|
+
f"Cannot recover E2B network policy for sandbox {sandbox_id}: "
|
|
284
|
+
"the applied deny/allow-internet policy is not representable by agent-env"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
allow_out = cls._info_field(network, "allow_out", missing)
|
|
288
|
+
allow_out_missing = allow_out is missing or cls._is_sdk_unset(allow_out)
|
|
289
|
+
has_deny_all = deny_out == [E2B_ALL_TRAFFIC]
|
|
290
|
+
if allow_out_missing and not has_deny_all:
|
|
291
|
+
return NetworkPolicy()
|
|
292
|
+
if allow_out_missing:
|
|
293
|
+
allow_out = []
|
|
294
|
+
elif not has_deny_all:
|
|
295
|
+
raise RuntimeError(
|
|
296
|
+
f"Cannot recover E2B network policy for sandbox {sandbox_id}: "
|
|
297
|
+
"allow_out was present without E2B's canonical deny-all rule"
|
|
298
|
+
)
|
|
299
|
+
if not isinstance(allow_out, (list, tuple)) or not all(
|
|
300
|
+
isinstance(destination, str) and destination for destination in allow_out
|
|
301
|
+
):
|
|
302
|
+
raise RuntimeError(
|
|
303
|
+
f"Cannot recover E2B network policy for sandbox {sandbox_id}: "
|
|
304
|
+
f"unexpected allow_out value {allow_out!r}"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
hosts: list[str] = []
|
|
308
|
+
cidrs: list[str] = []
|
|
309
|
+
for destination in allow_out:
|
|
310
|
+
try:
|
|
311
|
+
ip_network(destination, strict=False)
|
|
312
|
+
except ValueError:
|
|
313
|
+
hosts.append(destination)
|
|
314
|
+
else:
|
|
315
|
+
cidrs.append(destination)
|
|
316
|
+
return NetworkPolicy(
|
|
317
|
+
mode=NetworkMode.ALLOWLIST,
|
|
318
|
+
allow_hosts=tuple(hosts),
|
|
319
|
+
allow_cidrs=tuple(cidrs),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def _exposed_ports_from_info(cls, info: Any, *, sandbox_id: str) -> tuple[int, ...]:
|
|
324
|
+
"""Recover the provider-managed tunnel capabilities stored at creation."""
|
|
325
|
+
missing = object()
|
|
326
|
+
metadata = cls._info_field(info, "metadata", missing)
|
|
327
|
+
if metadata is missing or cls._is_sdk_unset(metadata) or metadata is None:
|
|
328
|
+
return ()
|
|
329
|
+
if not isinstance(metadata, Mapping):
|
|
330
|
+
logger.warning(
|
|
331
|
+
"Cannot recover exposed ports for E2B sandbox %s: unexpected metadata %r",
|
|
332
|
+
sandbox_id,
|
|
333
|
+
metadata,
|
|
334
|
+
)
|
|
335
|
+
return ()
|
|
336
|
+
encoded = metadata.get(_EXPOSED_PORTS_METADATA_KEY)
|
|
337
|
+
if not encoded:
|
|
338
|
+
return ()
|
|
339
|
+
try:
|
|
340
|
+
ports = tuple(
|
|
341
|
+
dict.fromkeys(int(value) for value in str(encoded).split(","))
|
|
342
|
+
)
|
|
343
|
+
if any(port < 1 or port > 65535 for port in ports):
|
|
344
|
+
raise ValueError
|
|
345
|
+
return ports
|
|
346
|
+
except ValueError:
|
|
347
|
+
logger.warning(
|
|
348
|
+
"Cannot recover exposed ports for E2B sandbox %s: invalid %s=%r",
|
|
349
|
+
sandbox_id,
|
|
350
|
+
_EXPOSED_PORTS_METADATA_KEY,
|
|
351
|
+
encoded,
|
|
352
|
+
)
|
|
353
|
+
return ()
|
|
354
|
+
|
|
355
|
+
async def get_sandbox(self, sandbox_id: str) -> E2BSandbox:
|
|
356
|
+
sandbox = await E2BSandbox.reconnect(
|
|
357
|
+
sandbox_id,
|
|
358
|
+
api_key=self._api_key,
|
|
359
|
+
sandbox_cls=self._get_sandbox_cls(),
|
|
360
|
+
)
|
|
361
|
+
try:
|
|
362
|
+
info = await sandbox._sandbox.get_info()
|
|
363
|
+
for port in self._exposed_ports_from_info(info, sandbox_id=sandbox_id):
|
|
364
|
+
sandbox.get_host(port)
|
|
365
|
+
sandbox.network_policy = self._network_policy_from_info(
|
|
366
|
+
info, sandbox_id=sandbox_id
|
|
367
|
+
)
|
|
368
|
+
except Exception as exc:
|
|
369
|
+
logger.warning(
|
|
370
|
+
"Could not recover the applied E2B network policy for sandbox %s; "
|
|
371
|
+
"reconnected for inspection/cleanup, but image loading will fail closed: %s",
|
|
372
|
+
sandbox_id,
|
|
373
|
+
exc,
|
|
374
|
+
)
|
|
375
|
+
sandbox.network_policy = None
|
|
376
|
+
return sandbox
|