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,237 @@
1
+ """Environment providers: the contract a provider implements, the built-ins' shared base, and finding a provider by type."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import time
8
+ from abc import ABC, abstractmethod
9
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, ClassVar
10
+
11
+ import httpx
12
+ from mcp import ClientSession
13
+ from mcp.client.streamable_http import streamable_http_client
14
+ from agent_env.env.env import DeployedEnv
15
+ from agent_env.env.gateway.constants import WELL_KNOWN_PATH
16
+ from agent_env.plugins import _registration
17
+ from agent_env.providers.sandbox_providers.sandbox import Sandbox
18
+ from agent_env.providers.sandbox_providers.sandbox_provider import SANDBOX_MODE_CONTAINER
19
+
20
+ if TYPE_CHECKING:
21
+ from agent_env.config.runtime import Config
22
+ from agent_env.env.env import Env
23
+ from agent_env.providers.sandbox_providers.sandbox_provider import SandboxProvider
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class EnvironmentProvider(ABC):
29
+ """Deploys an env and hands back its record; tears down what it created."""
30
+
31
+ type: ClassVar[str] # the name an env declares it by, and that its records carry as env_provider_type
32
+
33
+ @abstractmethod
34
+ async def deploy(self, env: Env, sandbox_provider: SandboxProvider) -> DeployedEnv:
35
+ """Deploy the env and return its record, unregistered: the env registers it. Take ``**options``, which receives every option
36
+ the env's deploy was given; a deploy that names its options instead is refused any other one set away from its default."""
37
+
38
+ @abstractmethod
39
+ async def close(self) -> None:
40
+ """Tear down everything deploy() created."""
41
+
42
+
43
+ def record_class_for(env_provider_type: str | None) -> type[DeployedEnv] | None:
44
+ """The record class a built-in provider type writes; None for any other type, whose records load by shape."""
45
+ provider = _builtin_env_providers().get(env_provider_type)
46
+ return provider.record_class if provider is not None else None
47
+
48
+
49
+ def build_env_provider(env_provider_type: str) -> EnvironmentProvider:
50
+ """A fresh provider of the type an env declares, built in or from a plugin; raises for a type this process can't find."""
51
+ return _env_provider_class(env_provider_type)()
52
+
53
+
54
+ def _env_provider_class(env_provider_type: str) -> type[EnvironmentProvider]:
55
+ """The registered provider class of that type; raises, naming the registered types, for one this process can't find."""
56
+ from agent_env.config import runtime
57
+
58
+ providers = runtime.get_config().env_provider_registry()
59
+ if env_provider_type not in providers:
60
+ raise ValueError(f"Unknown env_provider_type: {env_provider_type!r} (expected one of {sorted(providers)})"
61
+ f"{_registration.failure_note(_registration.ENV_PROVIDERS, env_provider_type)}")
62
+ return providers[env_provider_type]
63
+
64
+
65
+ class _SandboxEnvironmentProvider(EnvironmentProvider):
66
+ """The built-in providers' base: tracks the containers it creates in our sandboxes, closes them, reattaches to the records it wrote,
67
+ and retries across a chained spec."""
68
+
69
+ record_class: ClassVar[type[DeployedEnv]] = DeployedEnv # the class its records load back as
70
+
71
+ def __init__(self):
72
+ self._container_sandboxes: list[Sandbox] = []
73
+ self._environment_sandboxes: dict[str, Sandbox] = {}
74
+
75
+ def environment_sandbox(self, environment_name: str) -> Sandbox | None:
76
+ return self._environment_sandboxes.get(environment_name)
77
+
78
+ async def close(self) -> None:
79
+ for sb in self._container_sandboxes:
80
+ try:
81
+ await sb.terminate()
82
+ except Exception as e:
83
+ logger.warning(f"Failed to terminate {type(sb).__name__} {sb.sandbox_id}: {e}")
84
+ self._container_sandboxes.clear()
85
+ self._environment_sandboxes.clear()
86
+
87
+ async def _reattach(self, env: Env, deployed: DeployedEnv) -> Sandbox:
88
+ """A record this provider wrote, reattached: its primary sandbox, or in container mode the server's container, with every recorded one for close()."""
89
+ from agent_env.env.envs.mcp_server import MCPServerEnv
90
+ from agent_env.providers import build_sandbox_provider, get_env_sandbox_provider
91
+ provider = build_sandbox_provider(deployed.sandbox_type) if deployed.sandbox_type else get_env_sandbox_provider()
92
+ sandbox = await provider.get_sandbox(deployed.sandbox_id)
93
+ if env.environment_name in deployed.sandbox_ids.get(MCPServerEnv.type, {}):
94
+ # Container mode.
95
+ sandbox = await self._reattach_containers(provider, sandbox, deployed.sandbox_ids, env.environment_name)
96
+ return sandbox
97
+
98
+ async def _reattach_containers(self, provider: SandboxProvider, gateway: Sandbox, sandbox_ids: dict, environment_name: str) -> Sandbox:
99
+ """Reattach the recorded containers for close(), skipping a dead servicedb or sidecar; returns the server's container, raising if it's dead."""
100
+ from agent_env.env.envs.mcp_server import MCPServerEnv
101
+ from agent_env.env.envs.service_db import ServiceDBEnv
102
+ server_id = sandbox_ids[MCPServerEnv.type][environment_name]
103
+ db_ids = list(sandbox_ids.get(ServiceDBEnv.type, {}).values())
104
+ server, *dbs = await asyncio.gather(*(provider.get_sandbox(i) for i in [server_id, *db_ids]), return_exceptions=True)
105
+ if isinstance(server, BaseException):
106
+ raise RuntimeError(f"[{environment_name}] can't reattach the server's container {server_id}: {type(server).__name__}: {server}") from server
107
+ server.mode = SANDBOX_MODE_CONTAINER # recorded under mcp_server, so create_container made it
108
+ for db_id, db in zip(db_ids, dbs):
109
+ if isinstance(db, BaseException):
110
+ logger.warning(f"[{environment_name}] skipping recorded container {db_id}: {type(db).__name__}: {db}")
111
+ self._container_sandboxes = [server, *(db for db in dbs if not isinstance(db, BaseException)), gateway]
112
+ return server
113
+
114
+ async def _wait_for_tunnel(self, url: str, timeout: int = 60) -> dict | None:
115
+ """Wait until the deployed env serves its card to this process; returns the card.
116
+
117
+ Probes the well-known document rather than ``/mcp``, whose healthy endpoint holds the
118
+ connection open. Only a 200 carrying a card counts, and that card is what the deploy
119
+ records. Returns None if none arrives within ``timeout`` attempts.
120
+ """
121
+ probe_url = f"{url}{WELL_KNOWN_PATH}"
122
+ logger.info(f"Waiting for tunnel to be ready (timeout={timeout}s) via {probe_url}...")
123
+ last = "no response"
124
+ async with httpx.AsyncClient() as client:
125
+ for i in range(timeout):
126
+ try:
127
+ response = await client.get(probe_url, timeout=5)
128
+ card = _card_from(response)
129
+ if card is not None:
130
+ logger.info(f" Tunnel ready (HTTP {response.status_code}) after {i+1}s")
131
+ return card
132
+ last = f"HTTP {response.status_code} {response.headers.get('content-type', '')}".rstrip()
133
+ except Exception as e:
134
+ last = f"error: {type(e).__name__}"
135
+ if i % 10 == 0:
136
+ logger.info(f" Still waiting... ({last})")
137
+ await asyncio.sleep(1)
138
+ logger.info(f" Tunnel not ready after {timeout}s (last: {last})")
139
+ return None
140
+
141
+ def _sandbox_providers(self, sandbox_provider: SandboxProvider, env_id: str | None) -> list[SandboxProvider]:
142
+ """The sandbox providers to try, in order: a chained one's members, or itself."""
143
+ from agent_env.providers.sandbox_providers.chained_sandbox_provider import ChainedSandboxProvider
144
+
145
+ if isinstance(sandbox_provider, ChainedSandboxProvider):
146
+ return list(sandbox_provider._providers)
147
+ return [sandbox_provider]
148
+
149
+ async def _run(self, sandbox_provider: SandboxProvider, env_id: str | None, attempt: Callable[[SandboxProvider], Awaitable[Any]]) -> Any:
150
+ """Run ``attempt`` on the sandbox provider, or on each chained member until one succeeds; a failed deploy is closed."""
151
+ try:
152
+ sandbox_providers = self._sandbox_providers(sandbox_provider, env_id)
153
+ if len(sandbox_providers) == 1:
154
+ return await self._attempt(sandbox_providers[0], env_id, attempt)
155
+ errors: list[tuple[str, Exception]] = []
156
+ for p in sandbox_providers:
157
+ try:
158
+ return await self._attempt(p, env_id, attempt)
159
+ except Exception as e:
160
+ errors.append((type(p).__name__, e))
161
+ await self.close()
162
+ detail = "; ".join(f"{n}: {e!r}" for n, e in errors)
163
+ raise RuntimeError(f"All {len(sandbox_providers)} chained providers failed to deploy: {detail}")
164
+ except Exception:
165
+ await self.close()
166
+ raise
167
+
168
+ async def _attempt(self, sandbox_provider: SandboxProvider, env_id: str | None, attempt: Callable[[SandboxProvider], Awaitable[Any]]) -> Any:
169
+ """One deploy on one sandbox provider, logged as an ``env_deploy_provider_attempt``."""
170
+ start = time.monotonic()
171
+ provider_name = type(sandbox_provider).__name__
172
+ try:
173
+ result = await attempt(sandbox_provider)
174
+ logger.info(f"env_deploy_provider_attempt env_id={env_id or 'unknown'} provider={provider_name} status=success duration_s={time.monotonic() - start:.1f}")
175
+ except Exception as e:
176
+ logger.warning(f"env_deploy_provider_attempt env_id={env_id or 'unknown'} provider={provider_name} status=failure duration_s={time.monotonic() - start:.1f} error_type={type(e).__name__} error={e!r}")
177
+ raise
178
+ return result
179
+
180
+
181
+ def _size_kwargs(cpu: float | None, memory_mb: int | None) -> dict:
182
+ """Forward only what the caller set, so the provider's own default stands otherwise.
183
+
184
+ Passing None through would not reach that default: the providers hand cpu/memory
185
+ straight to the backend, so an explicit None means "no reservation at all" rather than
186
+ "your floor".
187
+ """
188
+ kwargs: dict = {}
189
+ if cpu is not None:
190
+ kwargs["cpu"] = cpu
191
+ if memory_mb is not None:
192
+ kwargs["memory"] = memory_mb
193
+ return kwargs
194
+
195
+
196
+ def _card_from(response: httpx.Response) -> dict | None:
197
+ """The env card in a readiness response: a 200 whose JSON body is an object with a name."""
198
+ if response.status_code != 200:
199
+ return None
200
+ try:
201
+ card = response.json()
202
+ except ValueError:
203
+ return None
204
+ return card if isinstance(card, dict) and isinstance(card.get("name"), str) and card["name"] else None
205
+
206
+
207
+ async def _tool_names(mcp_url: str) -> list[str]:
208
+ """The server's tool names as it lists them, duplicates included."""
209
+ async with streamable_http_client(mcp_url) as (read_stream, write_stream, _):
210
+ async with ClientSession(read_stream, write_stream) as session:
211
+ await session.initialize()
212
+ return [tool.name for tool in (await session.list_tools()).tools]
213
+
214
+
215
+ def _builtin_env_providers() -> dict[str, type[_SandboxEnvironmentProvider]]:
216
+ """The built-in providers, by type."""
217
+ from agent_env.providers.env_providers.env_gateway_provider import EnvironmentGatewayProvider
218
+ from agent_env.providers.env_providers.env_server_provider import EnvironmentServerProvider
219
+
220
+ return {p.type: p for p in (EnvironmentGatewayProvider, EnvironmentServerProvider)}
221
+
222
+
223
+ def _build_registry(source: Config | None = None) -> dict[str, type[EnvironmentProvider]]:
224
+ """The built-in providers, then ``agent_env.env_providers`` plugins."""
225
+ registry: dict[str, type[EnvironmentProvider]] = dict(_builtin_env_providers())
226
+ _registration.merge(registry, _registration.ENV_PROVIDERS, _validate_plugin, source=source)
227
+ return registry
228
+
229
+
230
+ def _validate_plugin(name: str, loaded: Any) -> type[EnvironmentProvider]:
231
+ cls = _registration.require_subclass(loaded, EnvironmentProvider)
232
+ if getattr(cls, "type", None) != name:
233
+ raise TypeError(f"{cls.__qualname__} has type {getattr(cls, 'type', None)!r}; it must equal the entry-point "
234
+ "name, which its records carry as env_provider_type")
235
+ if problem := _registration.unimplemented(cls, EnvironmentProvider):
236
+ raise TypeError(problem)
237
+ return cls
@@ -0,0 +1,133 @@
1
+ """The server environment provider: one MCP server in its own container on the configured sandbox provider, with no gateway in front and no state store."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import functools
7
+ from datetime import datetime, timezone
8
+ from typing import TYPE_CHECKING, ClassVar, Optional
9
+
10
+ import anyio
11
+
12
+ from agent_env.attribution import Attribution
13
+ from agent_env.env.env import DeployedSandboxEnv
14
+ from agent_env.env.gateway import AGENT_ENV_GATEWAY_MCP_PORT
15
+ from agent_env.env.gateway.constants import WELL_KNOWN_PATH
16
+ from agent_env.providers.env_providers.env_provider import _SandboxEnvironmentProvider, _size_kwargs, _tool_names
17
+ from agent_env.providers.sandbox_providers.sandbox_provider import SandboxProvider
18
+
19
+ if TYPE_CHECKING:
20
+ from agent_env.env.env import Env
21
+
22
+
23
+ class EnvironmentServerProvider(_SandboxEnvironmentProvider):
24
+ """Deploys one MCP server with no gateway in front; the server's own card is the env card."""
25
+
26
+ type: ClassVar[str] = "server"
27
+ record_class = DeployedSandboxEnv
28
+
29
+ async def deploy(
30
+ self,
31
+ env: Env,
32
+ sandbox_provider: SandboxProvider,
33
+ *,
34
+ ttl_seconds: int = 10800,
35
+ disk_size_gb: float = 10,
36
+ cpu: float | None = None,
37
+ memory_mb: int | None = None,
38
+ priority: Optional[int] = None,
39
+ attribution: Optional[Attribution] = None,
40
+ ) -> DeployedSandboxEnv:
41
+ """The env's MCP server in its own container; returns the record once it serves its card and its tools."""
42
+ from agent_env.env.envs.multi_env import MultiEnv
43
+ from agent_env.env.envs.website import WebsiteEnv
44
+
45
+ if isinstance(env, (MultiEnv, WebsiteEnv)):
46
+ raise TypeError(f"env_provider_type '{self.type}' deploys one MCP server, not a {env.type} env")
47
+ attempt = functools.partial(
48
+ self._deploy_server, env, ttl_seconds=ttl_seconds, disk_size_gb=disk_size_gb,
49
+ cpu=cpu, memory_mb=memory_mb, priority=priority, attribution=attribution,
50
+ )
51
+ return await self._run(sandbox_provider, env.id, attempt)
52
+
53
+ async def install_changelog_triggers(self, environment_name: str) -> None:
54
+ """No state store, so no changelog to install."""
55
+
56
+ async def _deploy_server(
57
+ self,
58
+ env: Env,
59
+ sandbox_provider: SandboxProvider,
60
+ *,
61
+ ttl_seconds: int,
62
+ disk_size_gb: float,
63
+ cpu: float | None,
64
+ memory_mb: int | None,
65
+ priority: Optional[int],
66
+ attribution: Optional[Attribution],
67
+ ) -> DeployedSandboxEnv:
68
+ """One attempt: the server's container, then its card and tools; returns its record."""
69
+ from agent_env.env.envs.mcp_server import MCPServerEnv
70
+
71
+ name = env.environment_name
72
+ server = await sandbox_provider.create_container(
73
+ image_name=env.docker_image_artifact.image_name,
74
+ port=AGENT_ENV_GATEWAY_MCP_PORT,
75
+ env={
76
+ "ENVIRONMENT_NAME": name,
77
+ "MCP_HOST": "0.0.0.0", # IPv4 too: an IPv6-only "::" bind starves tunnels and published ports
78
+ "DATABASE_URL": f"sqlite:////tmp/{name}.sqlite",
79
+ },
80
+ **_size_kwargs(cpu, memory_mb),
81
+ disk_size_gb=disk_size_gb, timeout=ttl_seconds,
82
+ attribution=dict(attribution or {}),
83
+ priority=priority,
84
+ )
85
+ self._container_sandboxes.append(server)
86
+ self._environment_sandboxes[name] = server
87
+ server_url = server.tunnel_urls[AGENT_ENV_GATEWAY_MCP_PORT]
88
+ try:
89
+ async with asyncio.timeout(_READINESS_TIMEOUT_S):
90
+ card = await self._wait_for_tunnel(server_url, timeout=int(_READINESS_TIMEOUT_S))
91
+ except TimeoutError:
92
+ card = None
93
+ if card is None:
94
+ raise RuntimeError(f"'{name}' did not serve its env card at {server_url}{WELL_KNOWN_PATH} in time; "
95
+ "an env deployed without a gateway must serve its own card")
96
+ card_read_at_utc = datetime.now(timezone.utc).isoformat()
97
+ if card["name"] != name:
98
+ raise RuntimeError(f"'{name}' serves a card named '{card['name']}'; "
99
+ "an env deployed without a gateway must serve its card under its environment_name")
100
+ record = DeployedSandboxEnv(
101
+ env_id=env.id,
102
+ env_version=env.version,
103
+ env_provider_type=self.type,
104
+ environment_card_url=f"{server_url}{WELL_KNOWN_PATH}",
105
+ environment_card=card,
106
+ environment_card_read_at_utc=card_read_at_utc,
107
+ sandbox_id=server.sandbox_id,
108
+ sandbox_type=server.type,
109
+ sandbox_ids={MCPServerEnv.type: {name: server.sandbox_id}},
110
+ )
111
+ await _require_tools(name, record.mcp_url)
112
+ return record
113
+
114
+
115
+ # Wall-clock cap on the server's readiness: attempts alone don't bound a tunnel that hangs each request.
116
+ _READINESS_TIMEOUT_S = 180.0
117
+
118
+ _TOOLS_GATE_TIMEOUT_S = 60.0
119
+
120
+
121
+ async def _require_tools(environment_name: str, mcp_url: str) -> None:
122
+ """Fail unless the server at ``mcp_url`` lists at least one MCP tool and no tool name twice."""
123
+ try:
124
+ # anyio re-cancels in cleanup; asyncio.timeout would wait out the MCP client's session-closing DELETE (up to 300 s).
125
+ with anyio.fail_after(_TOOLS_GATE_TIMEOUT_S):
126
+ tools = await _tool_names(mcp_url)
127
+ except Exception as e:
128
+ raise RuntimeError(f"'{environment_name}' did not list its MCP tools at {mcp_url}: {e!r}") from e
129
+ if not tools:
130
+ raise RuntimeError(f"'{environment_name}' lists no MCP tools at {mcp_url}; an env deployed without a gateway must serve at least one")
131
+ duplicates = sorted({t for t in tools if tools.count(t) > 1})
132
+ if duplicates:
133
+ raise RuntimeError(f"'{environment_name}' lists duplicate MCP tool names at {mcp_url}: {', '.join(duplicates)}")
@@ -0,0 +1,49 @@
1
+ """Env-state providers — abstract WHERE an environment's data/state lives"""
2
+
3
+ from agent_env.providers.env_state.env_state_provider import (
4
+ DatabaseStateProvider,
5
+ DEFAULT_STATE_TTL_SECONDS,
6
+ EnvStateInstance,
7
+ EnvStateProvider,
8
+ StateContext,
9
+ acquire_state_for_deploy,
10
+ build_state_provider,
11
+ LOCAL_POSTGRES_STATE_TYPE,
12
+ )
13
+ from agent_env.providers.env_state.local_postgres import (
14
+ LocalPostgresStateContext,
15
+ LocalPostgresStateProvider,
16
+ LocalPostgresStoreSpec,
17
+ )
18
+ from agent_env.providers.env_state.store import (
19
+ ENV_STATE_INSTANCES_COLLECTION,
20
+ EnvStateInstanceStore,
21
+ get_env_state_instance_store,
22
+ register_env_state_instance,
23
+ reset_env_state_instance_store,
24
+ retire_env_state_instance,
25
+ set_env_state_instance_store,
26
+ update_env_state_instance_metadata,
27
+ )
28
+
29
+ __all__ = [
30
+ "DatabaseStateProvider",
31
+ "DEFAULT_STATE_TTL_SECONDS",
32
+ "EnvStateInstance",
33
+ "EnvStateProvider",
34
+ "StateContext",
35
+ "acquire_state_for_deploy",
36
+ "build_state_provider",
37
+ "LOCAL_POSTGRES_STATE_TYPE",
38
+ "LocalPostgresStateContext",
39
+ "LocalPostgresStateProvider",
40
+ "LocalPostgresStoreSpec",
41
+ "ENV_STATE_INSTANCES_COLLECTION",
42
+ "EnvStateInstanceStore",
43
+ "get_env_state_instance_store",
44
+ "set_env_state_instance_store",
45
+ "reset_env_state_instance_store",
46
+ "register_env_state_instance",
47
+ "retire_env_state_instance",
48
+ "update_env_state_instance_metadata",
49
+ ]