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.
Files changed (312) hide show
  1. agent_env/__init__.py +0 -0
  2. agent_env/a2a_agent/__init__.py +23 -0
  3. agent_env/a2a_agent/a2a_agent.py +600 -0
  4. agent_env/a2a_agent/conversation_store.py +356 -0
  5. agent_env/a2a_agent/object_transfer.py +609 -0
  6. agent_env/a2a_agent/protocol.py +238 -0
  7. agent_env/a2a_agent/store.py +201 -0
  8. agent_env/a2a_agent/validator.py +901 -0
  9. agent_env/artifact/__init__.py +48 -0
  10. agent_env/artifact/artifact.py +74 -0
  11. agent_env/artifact/artifacts/__init__.py +0 -0
  12. agent_env/artifact/artifacts/cli.py +83 -0
  13. agent_env/artifact/artifacts/docker_image.py +457 -0
  14. agent_env/artifact/artifacts/environment.py +89 -0
  15. agent_env/artifact/artifacts/environment_universe.py +156 -0
  16. agent_env/artifact/artifacts/file.py +214 -0
  17. agent_env/artifact/artifacts/file_artifact_universe.py +206 -0
  18. agent_env/artifact/artifacts/skill.py +206 -0
  19. agent_env/artifact/artifacts/vm_image.py +95 -0
  20. agent_env/artifact/ref.py +15 -0
  21. agent_env/artifact/registry.py +251 -0
  22. agent_env/artifact/store.py +295 -0
  23. agent_env/artifact/universe.py +38 -0
  24. agent_env/attribution.py +41 -0
  25. agent_env/bundle/__init__.py +4 -0
  26. agent_env/bundle/_fs.py +128 -0
  27. agent_env/bundle/authoring.py +239 -0
  28. agent_env/bundle/installed.py +175 -0
  29. agent_env/bundle/ledger.py +269 -0
  30. agent_env/bundle/materialize.py +204 -0
  31. agent_env/bundle/parse.py +594 -0
  32. agent_env/bundle/plan.py +414 -0
  33. agent_env/bundle/resolve.py +404 -0
  34. agent_env/bundle/run.py +361 -0
  35. agent_env/cli/__init__.py +69 -0
  36. agent_env/cli/__main__.py +10 -0
  37. agent_env/cli/_installers.py +576 -0
  38. agent_env/cli/_plugin_changes.py +947 -0
  39. agent_env/cli/a2a_agent/__init__.py +22 -0
  40. agent_env/cli/a2a_agent/add_skill.py +97 -0
  41. agent_env/cli/a2a_agent/deploy.py +60 -0
  42. agent_env/cli/a2a_agent/get.py +15 -0
  43. agent_env/cli/a2a_agent/get_instance.py +26 -0
  44. agent_env/cli/a2a_agent/put.py +78 -0
  45. agent_env/cli/a2a_agent/validate.py +56 -0
  46. agent_env/cli/artifact/__init__.py +20 -0
  47. agent_env/cli/artifact/cli.py +83 -0
  48. agent_env/cli/artifact/environment.py +44 -0
  49. agent_env/cli/artifact/environment_universe.py +163 -0
  50. agent_env/cli/artifact/file_artifact_universe.py +281 -0
  51. agent_env/cli/artifact/skill.py +128 -0
  52. agent_env/cli/banner.py +25 -0
  53. agent_env/cli/config.py +263 -0
  54. agent_env/cli/env/__init__.py +31 -0
  55. agent_env/cli/env/deploy.py +112 -0
  56. agent_env/cli/env/gateway.py +57 -0
  57. agent_env/cli/env/get_instance.py +40 -0
  58. agent_env/cli/env/mcp_server.py +410 -0
  59. agent_env/cli/env/multi.py +282 -0
  60. agent_env/cli/env/service_db.py +99 -0
  61. agent_env/cli/env/snapshot.py +35 -0
  62. agent_env/cli/env/state/__init__.py +6 -0
  63. agent_env/cli/env/state/init.py +108 -0
  64. agent_env/cli/env/state/teardown.py +44 -0
  65. agent_env/cli/env/website.py +216 -0
  66. agent_env/cli/env/website_browser.py +67 -0
  67. agent_env/cli/eval/__init__.py +16 -0
  68. agent_env/cli/eval/add_tasks.py +63 -0
  69. agent_env/cli/eval/create.py +49 -0
  70. agent_env/cli/eval/run.py +194 -0
  71. agent_env/cli/identity/__init__.py +5 -0
  72. agent_env/cli/identity/client_identity.py +37 -0
  73. agent_env/cli/plugin.py +438 -0
  74. agent_env/cli/run.py +285 -0
  75. agent_env/cli/task/__init__.py +21 -0
  76. agent_env/cli/task/create.py +109 -0
  77. agent_env/cli/task/get.py +17 -0
  78. agent_env/cli/task/get_instance.py +34 -0
  79. agent_env/cli/task/run.py +615 -0
  80. agent_env/cli/task/validate.py +31 -0
  81. agent_env/cli/up.py +123 -0
  82. agent_env/cli/utils.py +183 -0
  83. agent_env/config/__init__.py +50 -0
  84. agent_env/config/describe.py +1067 -0
  85. agent_env/config/errors.py +13 -0
  86. agent_env/config/loader.py +245 -0
  87. agent_env/config/model.py +154 -0
  88. agent_env/config/paths.py +25 -0
  89. agent_env/config/plugin_tables.py +101 -0
  90. agent_env/config/provenance.py +134 -0
  91. agent_env/config/runtime.py +1064 -0
  92. agent_env/config/snapshot.py +81 -0
  93. agent_env/entity_refs.py +186 -0
  94. agent_env/env/__init__.py +49 -0
  95. agent_env/env/env.py +433 -0
  96. agent_env/env/env_artifact_store.py +87 -0
  97. agent_env/env/envs/__init__.py +7 -0
  98. agent_env/env/envs/_deployment.py +281 -0
  99. agent_env/env/envs/gateway_server.py +32 -0
  100. agent_env/env/envs/mcp_server.py +387 -0
  101. agent_env/env/envs/multi_env.py +780 -0
  102. agent_env/env/envs/service_db/Dockerfile +20 -0
  103. agent_env/env/envs/service_db/Dockerfile.db-mcp +42 -0
  104. agent_env/env/envs/service_db/Dockerfile.db-web +8 -0
  105. agent_env/env/envs/service_db.py +92 -0
  106. agent_env/env/envs/website.py +294 -0
  107. agent_env/env/envs/website_browser/Dockerfile +40 -0
  108. agent_env/env/envs/website_browser/__init__.py +17 -0
  109. agent_env/env/envs/website_browser/entrypoint.sh +13 -0
  110. agent_env/env/gateway/Dockerfile +14 -0
  111. agent_env/env/gateway/__init__.py +37 -0
  112. agent_env/env/gateway/clock.py +184 -0
  113. agent_env/env/gateway/constants.py +190 -0
  114. agent_env/env/gateway/entrypoint.py +156 -0
  115. agent_env/env/gateway/gateway.py +1095 -0
  116. agent_env/env/gateway/get_time.py +60 -0
  117. agent_env/env/gateway/requirements.txt +4 -0
  118. agent_env/env/gateway/triggers.py +1193 -0
  119. agent_env/env/legacy_protocol.py +146 -0
  120. agent_env/env/registry.py +93 -0
  121. agent_env/env/snapshot_store.py +372 -0
  122. agent_env/env/store.py +411 -0
  123. agent_env/eval/__init__.py +14 -0
  124. agent_env/eval/eval.py +68 -0
  125. agent_env/eval/store.py +116 -0
  126. agent_env/examples/hello/README.md +9 -0
  127. agent_env/examples/hello/artifacts/greeting/check.sh +1 -0
  128. agent_env/examples/hello/artifacts/greeting/hello.txt +1 -0
  129. agent_env/examples/hello/tasks/hello.json +10 -0
  130. agent_env/explorer/__init__.py +12 -0
  131. agent_env/explorer/app.py +277 -0
  132. agent_env/explorer/openapi_docs.py +242 -0
  133. agent_env/explorer/plugin.py +95 -0
  134. agent_env/explorer/routers/__init__.py +1 -0
  135. agent_env/explorer/routers/common.py +174 -0
  136. agent_env/explorer/routers/conversations.py +25 -0
  137. agent_env/explorer/routers/objects.py +112 -0
  138. agent_env/explorer/routers/runs.py +475 -0
  139. agent_env/plugins/__init__.py +59 -0
  140. agent_env/plugins/_cli.py +244 -0
  141. agent_env/plugins/_discovery.py +97 -0
  142. agent_env/plugins/_inventory.py +262 -0
  143. agent_env/plugins/_registration.py +268 -0
  144. agent_env/plugins/_report.py +47 -0
  145. agent_env/plugins/_requirements.py +55 -0
  146. agent_env/providers/__init__.py +37 -0
  147. agent_env/providers/env_providers/__init__.py +12 -0
  148. agent_env/providers/env_providers/constants.py +13 -0
  149. agent_env/providers/env_providers/env_gateway_provider.py +1354 -0
  150. agent_env/providers/env_providers/env_provider.py +237 -0
  151. agent_env/providers/env_providers/env_server_provider.py +133 -0
  152. agent_env/providers/env_state/__init__.py +49 -0
  153. agent_env/providers/env_state/env_state_provider.py +554 -0
  154. agent_env/providers/env_state/local_postgres.py +390 -0
  155. agent_env/providers/env_state/store.py +112 -0
  156. agent_env/providers/sandbox_providers/__init__.py +29 -0
  157. agent_env/providers/sandbox_providers/chained_sandbox_provider.py +93 -0
  158. agent_env/providers/sandbox_providers/e2b/__init__.py +6 -0
  159. agent_env/providers/sandbox_providers/e2b/provider.py +376 -0
  160. agent_env/providers/sandbox_providers/e2b/sandbox.py +342 -0
  161. agent_env/providers/sandbox_providers/e2b/template.py +158 -0
  162. agent_env/providers/sandbox_providers/local_sandbox.py +408 -0
  163. agent_env/providers/sandbox_providers/modal_sandbox.py +500 -0
  164. agent_env/providers/sandbox_providers/modal_vm_sandbox.py +466 -0
  165. agent_env/providers/sandbox_providers/sandbox.py +387 -0
  166. agent_env/providers/sandbox_providers/sandbox_provider.py +614 -0
  167. agent_env/py.typed +0 -0
  168. agent_env/runner/__init__.py +16 -0
  169. agent_env/runner/local_runner.py +190 -0
  170. agent_env/runner/runner.py +127 -0
  171. agent_env/runner/store.py +94 -0
  172. agent_env/store/__init__.py +126 -0
  173. agent_env/store/_google.py +68 -0
  174. agent_env/store/base.py +33 -0
  175. agent_env/store/document_store/__init__.py +60 -0
  176. agent_env/store/document_store/document_store.py +519 -0
  177. agent_env/store/document_store/dynamodb_document_store.py +306 -0
  178. agent_env/store/document_store/evaluation.py +199 -0
  179. agent_env/store/document_store/firestore_mongo_document_store.py +242 -0
  180. agent_env/store/document_store/mongo_document_store.py +259 -0
  181. agent_env/store/document_store/sqlite_document_store.py +314 -0
  182. agent_env/store/ids.py +123 -0
  183. agent_env/store/image_store/__init__.py +34 -0
  184. agent_env/store/image_store/ecr_image_store.py +131 -0
  185. agent_env/store/image_store/google_credentials.py +113 -0
  186. agent_env/store/image_store/image_store.py +113 -0
  187. agent_env/store/image_store/local_registry_image_store.py +106 -0
  188. agent_env/store/image_store/oci_registry_credentials.py +196 -0
  189. agent_env/store/local_state.py +27 -0
  190. agent_env/store/object_store/__init__.py +24 -0
  191. agent_env/store/object_store/gcs_object_store.py +415 -0
  192. agent_env/store/object_store/local_object_store.py +143 -0
  193. agent_env/store/object_store/object_store.py +198 -0
  194. agent_env/store/object_store/s3_object_store.py +354 -0
  195. agent_env/store/query.py +112 -0
  196. agent_env/store/routing.py +609 -0
  197. agent_env/store/secret_store/__init__.py +19 -0
  198. agent_env/store/secret_store/aws_secrets_manager_secret_store.py +293 -0
  199. agent_env/store/secret_store/gcp_secret_manager_secret_store.py +319 -0
  200. agent_env/store/secret_store/local_secret_store.py +47 -0
  201. agent_env/store/secret_store/secret_store.py +19 -0
  202. agent_env/task/__init__.py +27 -0
  203. agent_env/task/interrupts.py +109 -0
  204. agent_env/task/registry.py +27 -0
  205. agent_env/task/step_journal.py +99 -0
  206. agent_env/task/store.py +940 -0
  207. agent_env/task/task.py +815 -0
  208. agent_env/task/teardown.py +142 -0
  209. agent_env/task_step/__init__.py +47 -0
  210. agent_env/task_step/context.py +195 -0
  211. agent_env/task_step/context_ops.py +224 -0
  212. agent_env/task_step/registry.py +182 -0
  213. agent_env/task_step/review_store.py +118 -0
  214. agent_env/task_step/snapshot_utils/__init__.py +2 -0
  215. agent_env/task_step/snapshot_utils/agent_state_capture.py +261 -0
  216. agent_env/task_step/snapshot_utils/snapshot_series.py +631 -0
  217. agent_env/task_step/store.py +129 -0
  218. agent_env/task_step/task_step.py +173 -0
  219. agent_env/task_step/task_steps/__init__.py +27 -0
  220. agent_env/task_step/task_steps/a2a_agent_validator/__init__.py +31 -0
  221. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/README.md +158 -0
  222. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.m4a +0 -0
  223. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp3 +0 -0
  224. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp4 +0 -0
  225. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.ogg +0 -0
  226. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.wav +0 -0
  227. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/document.pdf +0 -0
  228. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.gif +0 -0
  229. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.jpg +0 -0
  230. agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.png +0 -0
  231. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_card.py +103 -0
  232. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_config_identity.py +69 -0
  233. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_mcp.py +178 -0
  234. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_core_protocol.py +67 -0
  235. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_install.py +115 -0
  236. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution.py +124 -0
  237. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution_runtime.py +173 -0
  238. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_modalities.py +270 -0
  239. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_peer_agents.py +102 -0
  240. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_role.py +151 -0
  241. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_skill_config.py +194 -0
  242. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_snapshot.py +111 -0
  243. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_system_prompt.py +215 -0
  244. agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_trajectory.py +164 -0
  245. agent_env/task_step/task_steps/add_skills.py +240 -0
  246. agent_env/task_step/task_steps/apply_server_config.py +288 -0
  247. agent_env/task_step/task_steps/collect_artifacts.py +940 -0
  248. agent_env/task_step/task_steps/deploy_agent.py +626 -0
  249. agent_env/task_step/task_steps/deploy_env.py +197 -0
  250. agent_env/task_step/task_steps/deploy_human_agent.py +100 -0
  251. agent_env/task_step/task_steps/deploy_sandbox.py +170 -0
  252. agent_env/task_step/task_steps/env_card_validator/__init__.py +4 -0
  253. agent_env/task_step/task_steps/env_card_validator/verify_env_card.py +109 -0
  254. agent_env/task_step/task_steps/env_card_validator/verify_env_core_protocol.py +108 -0
  255. agent_env/task_step/task_steps/install_agent.py +376 -0
  256. agent_env/task_step/task_steps/load_artifact.py +775 -0
  257. agent_env/task_step/task_steps/mcp_cli_builder/__init__.py +4 -0
  258. agent_env/task_step/task_steps/mcp_cli_builder/build_mcp_cli.py +201 -0
  259. agent_env/task_step/task_steps/mcp_cli_builder/codegen.py +627 -0
  260. agent_env/task_step/task_steps/mcp_env_validator/__init__.py +44 -0
  261. agent_env/task_step/task_steps/mcp_env_validator/validation_gate_aggregator.py +102 -0
  262. agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_env_assessment.py +78 -0
  263. agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_tool_schema.py +152 -0
  264. agent_env/task_step/task_steps/mcp_env_validator/verify_spec_conformance.py +486 -0
  265. agent_env/task_step/task_steps/modify_env_tool_access.py +80 -0
  266. agent_env/task_step/task_steps/multienv_validator/__init__.py +3 -0
  267. agent_env/task_step/task_steps/multienv_validator/combine_universe_verdicts.py +107 -0
  268. agent_env/task_step/task_steps/multienv_validator/universe_comparison.py +253 -0
  269. agent_env/task_step/task_steps/multienv_validator/verify_universe_agent_judge.py +180 -0
  270. agent_env/task_step/task_steps/multienv_validator/verify_universe_roundtrip.py +241 -0
  271. agent_env/task_step/task_steps/peer_agents.py +92 -0
  272. agent_env/task_step/task_steps/prompt_agent.py +900 -0
  273. agent_env/task_step/task_steps/register_agent_triggers.py +113 -0
  274. agent_env/task_step/task_steps/register_env_triggers.py +97 -0
  275. agent_env/task_step/task_steps/reset_env.py +71 -0
  276. agent_env/task_step/task_steps/review.py +152 -0
  277. agent_env/task_step/task_steps/run_code.py +461 -0
  278. agent_env/task_step/task_steps/run_code_runner.py +52 -0
  279. agent_env/task_step/task_steps/run_docker_container.py +380 -0
  280. agent_env/task_step/task_steps/sandbox_utils/__init__.py +0 -0
  281. agent_env/task_step/task_steps/sandbox_utils/sandbox_utils.py +79 -0
  282. agent_env/task_step/task_steps/snapshot_agent_state.py +205 -0
  283. agent_env/task_step/task_steps/snapshot_env.py +637 -0
  284. agent_env/task_step/task_steps/sync_env_clock.py +132 -0
  285. agent_env/task_step/task_steps/teardown_sandboxes.py +170 -0
  286. agent_env/task_step/task_steps/verifiers/__init__.py +23 -0
  287. agent_env/task_step/task_steps/verifiers/agent_prompt_response_verifier.py +166 -0
  288. agent_env/task_step/task_steps/verifiers/aggregate_verifiers.py +90 -0
  289. agent_env/task_step/task_steps/verifiers/env_outcome_verifier.py +127 -0
  290. agent_env/task_step/task_steps/verifiers/judge_utils/__init__.py +2 -0
  291. agent_env/task_step/task_steps/verifiers/judge_utils/frame_selection.py +377 -0
  292. agent_env/task_step/task_steps/verifiers/judge_utils/judge_output_format.py +1293 -0
  293. agent_env/task_step/task_steps/verifiers/judge_utils/trajectory_filter.py +514 -0
  294. agent_env/task_step/task_steps/verifiers/rubrics_verifier.py +1318 -0
  295. agent_env/task_step/task_steps/verifiers/run_container_unit_tests_verifier.py +434 -0
  296. agent_env/task_step/task_steps/verifiers/scoring.py +40 -0
  297. agent_env/task_step/task_steps/verifiers/verify_sandbox.py +308 -0
  298. agent_env/task_step/thread_work.py +61 -0
  299. agent_env/utils/__init__.py +0 -0
  300. agent_env/utils/card_naming.py +108 -0
  301. agent_env/utils/deprecation.py +109 -0
  302. agent_env/utils/docker_build.py +29 -0
  303. agent_env/utils/exec_retry.py +77 -0
  304. agent_env/utils/litellm_attribution.py +59 -0
  305. agent_env/utils/paths.py +20 -0
  306. agentenv_framework-0.9.1254.dist-info/METADATA +394 -0
  307. agentenv_framework-0.9.1254.dist-info/RECORD +312 -0
  308. agentenv_framework-0.9.1254.dist-info/WHEEL +4 -0
  309. agentenv_framework-0.9.1254.dist-info/entry_points.txt +5 -0
  310. agentenv_framework-0.9.1254.dist-info/licenses/LICENSE +202 -0
  311. agentenv_framework-0.9.1254.dist-info/licenses/NOTICE +4 -0
  312. 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