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,281 @@
1
+ """The env side of the environment-provider contract: deploying an env through its provider, the options a provider takes,
2
+ the checks on a plugin's record, loading into a deployment a plugin's provider made, and closing a deployment."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ import inspect
8
+ import logging
9
+ from typing import TYPE_CHECKING, Optional
10
+
11
+ from agentenv_protocol import METHOD_ADD, METHOD_RESET, RPC_PATH, FilePart, client as protocol_v1
12
+ from agent_env.env import legacy_protocol
13
+ from agent_env.env.env import DeployedSandboxEnv, gateway_url_of
14
+ from agent_env.env.store import register_env_instance
15
+ from agent_env.store.base import ObjectNotFoundError
16
+
17
+ if TYPE_CHECKING:
18
+ from agent_env.artifact import FileArtifact
19
+ from agent_env.env.env import DeployedEnv, Env
20
+ from agent_env.env.envs.mcp_server import MCPServerEnv
21
+ from agent_env.env.envs.website import WebsiteEnv
22
+ from agent_env.providers.env_providers.env_provider import EnvironmentProvider, _SandboxEnvironmentProvider
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ LOAD_OPERATIONS = (METHOD_RESET, METHOD_ADD) # the data-plane calls a load makes, in order, as a card lists them
27
+
28
+
29
+ async def deploy_through_provider(env: Env, *, environment_name: str | None, ttl_seconds: int, sandbox_type: str | None,
30
+ **options) -> DeployedEnv:
31
+ """Deploy env through the provider its env_provider_type names and register the record, the way each built-in env deploys.
32
+
33
+ The env's provider deploys it the first time; a deploy of an env that already has a deployment gets a provider of its own,
34
+ so that a failure closes only what it started and leaves the earlier deployment as it was. A deploy onto the env state
35
+ instance that deployment holds is refused, since the attempt's close() would retire it. A success keeps the earlier
36
+ deployment's provider and sandbox on the env, so its close() still tears that deployment down. A deploy refused (see
37
+ ``deploy_refusal``) or a bad attribution fails before anything changes. A built-in's sandbox is set on the env before the
38
+ record is registered, so close() reaches it; in container mode that is the container of the server named
39
+ ``environment_name``, None for a MultiEnv, whose servers each have their own. A plugin's record is checked instead.
40
+ """
41
+ from agent_env.providers import build_env_provider, build_sandbox_provider, get_env_sandbox_provider
42
+
43
+ held = getattr(env._deployed, "env_state_instance_ids", None) or []
44
+ if (instance := options.get("env_state_instance_id")) and instance in held:
45
+ raise ValueError(f"env '{env.id}' already has a deployment, instance {env._deployed.instance_id!r}, on env state instance "
46
+ f"{instance!r}; one state instance backs one deployment, so deploy another env object onto it")
47
+ reuse = env._env_provider is not None and env._deployed is None
48
+ provider = env._env_provider if reuse else build_env_provider(env.env_provider_type)
49
+ if options.get("attribution") is not None:
50
+ options["attribution"] = dict(options["attribution"])
51
+ options = provider_options(env, provider, ttl_seconds=ttl_seconds, **options)
52
+ sandbox_provider = build_sandbox_provider(sandbox_type) if sandbox_type else get_env_sandbox_provider()
53
+ earlier = env._env_provider, env._sandbox, env._gateway_url, env._instance_id, env._deployed, env._replaced
54
+ owned = env._deployed is not None and env._deployed.env_id == env.id # a MultiEnv's child holds its parent's record
55
+ replaced = [*env._replaced, (env._env_provider, env._sandbox)] if owned else env._replaced
56
+ env._env_provider, env._sandbox, env._replaced = provider, None, []
57
+ try:
58
+ deployed = await provider.deploy(env, sandbox_provider, **options)
59
+ if builtin := as_builtin(provider):
60
+ env._sandbox = builtin.environment_sandbox(environment_name) or builtin.sandbox # first, so a refusal's close() reaches it
61
+ if not isinstance(deployed, DeployedSandboxEnv):
62
+ raise TypeError(f"env_provider_type '{provider.type}' subclasses a built-in provider, so it is handled as one: its "
63
+ f"deploy() must return the built-in's kind of record, not a {type(deployed).__name__}")
64
+ else:
65
+ check_plugin_record(env, deployed)
66
+ env._gateway_url = gateway_url_of(deployed)
67
+ deployed = register_env_instance(deployed, ttl_seconds)
68
+ env._instance_id = deployed.instance_id
69
+ env._deployed, env._replaced = deployed, replaced
70
+ return deployed
71
+ except BaseException:
72
+ try:
73
+ await env.close()
74
+ finally:
75
+ env._env_provider, env._sandbox, env._gateway_url, env._instance_id, env._deployed, env._replaced = earlier
76
+ raise
77
+
78
+
79
+ async def close_replaced(env: Env) -> None:
80
+ """Tear down each deployment a later deploy of env replaced: its provider, then its sandbox. Each close() takes an entry
81
+ off the env before working on it, so two at once never share one, and a cancellation puts back what it didn't finish."""
82
+ while env._replaced:
83
+ provider, sandbox = env._replaced.pop(0)
84
+ try:
85
+ if provider is not None:
86
+ try:
87
+ await provider.close()
88
+ except Exception as e:
89
+ logger.warning(f"Failed to close the env provider of a replaced deployment: {e}")
90
+ provider = None
91
+ if sandbox is not None:
92
+ try:
93
+ await sandbox.terminate()
94
+ except Exception as e:
95
+ logger.warning(f"Failed to terminate sandbox {sandbox.sandbox_id}: {e}")
96
+ except BaseException:
97
+ env._replaced.insert(0, (provider, sandbox))
98
+ raise
99
+
100
+
101
+ async def load_by_signed_url(env: MCPServerEnv | WebsiteEnv, file_artifact: FileArtifact) -> None:
102
+ """Load into a server a plugin's provider deployed, which has no sandbox of ours to stage into: it fetches the file from a
103
+ signed URL. The server may be a MultiEnv's child, whose record is the MultiEnv's."""
104
+ from agent_env.config import get_config
105
+ from agent_env.env.gateway.constants import data_plane_load_timeout_s
106
+
107
+ deployed_by = f"env '{env._deployed.env_id}' was deployed by env_provider_type '{env._deployed.env_provider_type}'"
108
+ if not env._deployed.environment_card:
109
+ raise RuntimeError(f"{deployed_by}, whose record carries no env card, so '{env.environment_name}' has no data plane to load into")
110
+ base_url = await legacy_protocol.v1_base_url(env._deployed, env._gateway_url, env.environment_name, mcp=True)
111
+ operations = ((env._deployed.get_child_env_card(env.environment_name) or {}).get("capabilities") or {}).get("operations")
112
+ if base_url is None:
113
+ raise RuntimeError(f"{deployed_by}, whose card offers '{env.environment_name}' no data plane to load into")
114
+ if operations is not None and (missing := [op for op in LOAD_OPERATIONS if op not in operations]):
115
+ raise RuntimeError(f"{deployed_by}, whose card offers '{env.environment_name}' no {', '.join(missing)} operation to load with")
116
+ store = get_config().get_object_store_at(file_artifact.object_url)
117
+ metadata = await asyncio.to_thread(store.get_object_metadata_at, file_artifact.object_url)
118
+ if metadata is None:
119
+ raise ObjectNotFoundError(f"No object at {file_artifact.object_url}")
120
+ timeout = data_plane_load_timeout_s(metadata.size)
121
+ # Signed before the reset, so a store that can't sign leaves the env's data as it is; it lasts one timeout per call.
122
+ url = await asyncio.to_thread(store.signed_get_url, file_artifact.object_url, len(LOAD_OPERATIONS) * timeout)
123
+ if url is None:
124
+ raise RuntimeError(f"{deployed_by}, so its server fetches {file_artifact.object_url} itself, and {type(store).__name__} "
125
+ "can't sign a URL for it; loading into such an env needs an object store that signs URLs, such as S3")
126
+ await protocol_v1.reset_data(base_url, timeout=timeout)
127
+ await protocol_v1.add_data(base_url, [FilePart(file={
128
+ "uri": url,
129
+ "mimeType": file_artifact.content_type,
130
+ "name": file_artifact.filename,
131
+ })], timeout=timeout)
132
+
133
+
134
+ async def close_deployed(deployed: DeployedEnv, env_class: type[Env]) -> None:
135
+ """Close what a task deployed of an env_class env: a built-in's through its env, a plugin's by terminating the sandboxes its record names."""
136
+ from agent_env.task_step.task_steps.teardown_sandboxes import _env_sandbox_ids, _terminate
137
+
138
+ env = await env_class.from_deployed_env(deployed)
139
+ if as_builtin(env._env_provider):
140
+ await env.close()
141
+ return
142
+ sandboxes = _env_sandbox_ids(deployed)
143
+ if not sandboxes:
144
+ logger.warning(f"env '{deployed.env_id}' was deployed by env_provider_type '{deployed.env_provider_type}' outside "
145
+ "agent-env's sandboxes, so it isn't closed here; the deployment must end by itself")
146
+ results = await asyncio.gather(*(_terminate(sandbox_id, sandbox_type, "env") for sandbox_id, sandbox_type in sandboxes),
147
+ return_exceptions=True)
148
+ for (sandbox_id, _), result in zip(sandboxes, results):
149
+ if isinstance(result, BaseException):
150
+ logger.warning(f"env '{deployed.env_id}': terminate {sandbox_id} failed: {result!r}")
151
+
152
+
153
+ def check_plugin_record(env: Env, deployed: DeployedEnv) -> None:
154
+ """Refuse a record a plugin's provider returned that the env couldn't use: one of another type, without an MCP URL, whose card
155
+ isn't the env's or gives a child env a url that isn't a path, or for a MultiEnv doesn't list each of its servers and websites
156
+ or names its MCP server other than the env's name."""
157
+ from agent_env.env.envs.multi_env import MultiEnv
158
+
159
+ returned = f"env '{env.id}': env_provider_type '{env.env_provider_type}' returned a record"
160
+ if deployed.env_provider_type != env.env_provider_type:
161
+ raise RuntimeError(f"{returned} with env_provider_type {deployed.env_provider_type!r}; set it to {env.env_provider_type!r}")
162
+ if bool(deployed.environment_card) != bool(deployed.environment_card_url):
163
+ missing = "environment_card_url" if deployed.environment_card else "environment_card"
164
+ raise RuntimeError(f"{returned} with no {missing}; an env card and its environment_card_url come together")
165
+ if not deployed.mcp_url:
166
+ raise RuntimeError(f"{returned} with no MCP URL; a record carries it in its env card (environment_card and "
167
+ "environment_card_url) or in mcp_url")
168
+ if not deployed.environment_card:
169
+ if isinstance(env, MultiEnv):
170
+ raise RuntimeError(f"{returned} with no env card; a MultiEnv's children are reached through the card, which lists each "
171
+ "of its MCP servers and websites")
172
+ return
173
+ if isinstance(env, MultiEnv):
174
+ names = [child.environment_name for child in [*env.mcp_server_envs, *env.website_envs]]
175
+ if missing := [name for name in names if deployed.get_child_env_card(name) is None]:
176
+ raise RuntimeError(f"{returned} whose env card lists no child env named {', '.join(map(repr, missing))}; a MultiEnv's card "
177
+ "lists each of its MCP servers and websites by environment_name")
178
+ if env.name and deployed.mcp_server_name != env.name:
179
+ raise RuntimeError(f"{returned} whose MCP server is named {deployed.mcp_server_name!r}; agents see it under the "
180
+ f"record's mcp_server_name, or its card's name, which must be the env's name {env.name!r}")
181
+ elif deployed.get_child_env_card(env.environment_name) is None:
182
+ raise RuntimeError(f"{returned} whose env card is named {deployed.environment_card.get('name')!r}; it must be the env's "
183
+ f"environment_name {env.environment_name!r}, or list a child env of that name")
184
+ else:
185
+ names = [env.environment_name]
186
+ if not_paths := [name for name in names if not deployed.get_child_env_card(name).get("url", RPC_PATH).startswith("/")]:
187
+ raise RuntimeError(f"{returned} whose env card gives {', '.join(map(repr, not_paths))} a url that isn't a path; a child env's "
188
+ "url is a path under the record's address, such as '/svc/<name>/agentenv'")
189
+
190
+
191
+ def plugin_deployment(env: Env) -> DeployedEnv | None:
192
+ """The env's record if a plugin's provider deployed it, leaving no host of ours to stage onto; None for a built-in's, closed or not,
193
+ or no deployment."""
194
+ from agent_env.providers.env_providers.env_provider import record_class_for
195
+
196
+ deployed = env._deployed
197
+ if deployed is None or as_builtin(env._env_provider) is not None or record_class_for(deployed.env_provider_type) is not None:
198
+ return None
199
+ return deployed
200
+
201
+
202
+ def host_staging_refusal(env: Env, what: str) -> str | None:
203
+ """Why ``what``, which stages files onto the env's host, is refused: a plugin's provider deployed it; None otherwise."""
204
+ if (deployed := plugin_deployment(env)) is None:
205
+ return None
206
+ return f"{what} needs a built-in env provider; env '{deployed.env_id}' was deployed by env_provider_type '{deployed.env_provider_type}'"
207
+
208
+
209
+ def builtin_provider_for(env_provider_type: str) -> Optional[_SandboxEnvironmentProvider]:
210
+ """A fresh built-in provider of that type, or None for a plugin's type."""
211
+ from agent_env.providers.env_providers.env_provider import _builtin_env_providers
212
+ builtin = _builtin_env_providers().get(env_provider_type)
213
+ return builtin() if builtin is not None else None
214
+
215
+
216
+ def plugin_provider_like_a_builtin(env_provider_type: str) -> Optional[_SandboxEnvironmentProvider]:
217
+ """A fresh provider of a plugin's type that subclasses a built-in, so it reattaches as one; None for any other, or one not installed."""
218
+ from agent_env.providers.env_providers.env_provider import _SandboxEnvironmentProvider, _env_provider_class
219
+ try:
220
+ provider_class = _env_provider_class(env_provider_type)
221
+ except ValueError:
222
+ return None
223
+ return provider_class() if issubclass(provider_class, _SandboxEnvironmentProvider) else None
224
+
225
+
226
+ def as_builtin(provider: Optional[EnvironmentProvider]) -> Optional[_SandboxEnvironmentProvider]:
227
+ """The provider if it is a built-in or subclasses one, whose sandboxes loads stage into and reattach and close() reach; None for any other plugin's."""
228
+ from agent_env.providers.env_providers.env_provider import _SandboxEnvironmentProvider
229
+ return provider if isinstance(provider, _SandboxEnvironmentProvider) else None
230
+
231
+
232
+ def provider_options(env: Env, provider: EnvironmentProvider, **options) -> dict:
233
+ """The options provider takes: every one for a deploy(**options), else those it names, one it doesn't being dropped at deploy()'s
234
+ default; raises for a deploy refused (see ``deploy_refusal``)."""
235
+ if refusal := deploy_refusal(env, provider, options):
236
+ raise ValueError(refusal)
237
+ taken = inspect.signature(provider.deploy).parameters
238
+ if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in taken.values()):
239
+ return options
240
+ return {name: value for name, value in options.items() if name in taken}
241
+
242
+
243
+ def provider_or_class(env: Env) -> EnvironmentProvider | type[EnvironmentProvider]:
244
+ """The env's provider, or before one is built, the registered class its env_provider_type names; raises for a type this process can't find."""
245
+ from agent_env.providers.env_providers.env_provider import _env_provider_class
246
+ return env._env_provider if env._env_provider is not None else _env_provider_class(env.env_provider_type)
247
+
248
+
249
+ def deploy_refusal(env: Env, provider: EnvironmentProvider | type[EnvironmentProvider], options: dict) -> str | None:
250
+ """Why deploying env through provider, or a provider of that class, is refused before anything is built, or None: the server provider
251
+ for an env other than one MCP server, a plugin's MultiEnv with an MCP server and a website of one name, or an option it can't take."""
252
+ from agent_env.env.envs.mcp_server import MCPServerEnv
253
+ from agent_env.env.envs.multi_env import MultiEnv
254
+ from agent_env.providers.env_providers.env_provider import _SandboxEnvironmentProvider
255
+ from agent_env.providers.env_providers.env_server_provider import EnvironmentServerProvider
256
+
257
+ provider_class = provider if isinstance(provider, type) else type(provider)
258
+ refused = f"env '{env.id}' has env_provider_type '{env.env_provider_type}'"
259
+ if issubclass(provider_class, EnvironmentServerProvider) and not isinstance(env, MCPServerEnv):
260
+ return f"{refused}, which deploys one MCP server, not a {env.type} env"
261
+ if isinstance(env, MultiEnv) and not issubclass(provider_class, _SandboxEnvironmentProvider):
262
+ shared = {c.environment_name for c in env.mcp_server_envs} & {c.environment_name for c in env.website_envs}
263
+ if shared:
264
+ return (f"{refused}, whose one env card can't tell an MCP server and a website apart by name, and both are named "
265
+ f"{', '.join(map(repr, sorted(shared)))}")
266
+ return option_refusal(env, provider, options)
267
+
268
+
269
+ def option_refusal(env: Env, provider: EnvironmentProvider | type[EnvironmentProvider], options: dict) -> str | None:
270
+ """Why provider, or a provider of that class, wouldn't take options: one its deploy() doesn't name, set away from the env's deploy() default."""
271
+ from agent_env.providers.env_providers.env_provider import _SandboxEnvironmentProvider
272
+ taken = inspect.signature(provider.deploy).parameters
273
+ if any(p.kind is inspect.Parameter.VAR_KEYWORD for p in taken.values()):
274
+ return None
275
+ defaults = inspect.signature(type(env).deploy).parameters
276
+ refused = [name for name, value in options.items() if name not in taken and value != defaults[name].default]
277
+ if not refused:
278
+ return None
279
+ builtin = issubclass(provider if isinstance(provider, type) else type(provider), _SandboxEnvironmentProvider)
280
+ how = "" if builtin else "; name it in the provider's deploy() or take **options"
281
+ return f"env '{env.id}' has env_provider_type '{env.env_provider_type}', which doesn't take {', '.join(refused)}{how}"
@@ -0,0 +1,32 @@
1
+ """Gateway server environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import ClassVar, Optional
6
+
7
+ from agent_env.artifact import Artifact, DockerImageArtifact
8
+ from agent_env.env.env import Env
9
+
10
+
11
+ class GatewayEnv(Env):
12
+ type: ClassVar[str] = "gateway_server"
13
+ description = "Gateway server that orchestrates multiple MCP servers"
14
+
15
+ def __init__(self, id: str, version: Optional[int], docker_image_artifact: DockerImageArtifact, metadata: Optional[dict[str, str]] = None):
16
+ super().__init__(id, version, metadata=metadata)
17
+ self.docker_image_artifact = docker_image_artifact
18
+
19
+ def to_dict(self) -> dict:
20
+ base = super().to_dict()
21
+ base["docker_image_artifact"] = {
22
+ "id": self.docker_image_artifact.id,
23
+ "version": self.docker_image_artifact.version,
24
+ "type": self.docker_image_artifact.type,
25
+ }
26
+ return base
27
+
28
+ @classmethod
29
+ def from_dict(cls, data: dict) -> "GatewayEnv":
30
+ artifact_ref = data["docker_image_artifact"]
31
+ docker_image_artifact = Artifact.get(artifact_ref["id"], version=artifact_ref["version"])
32
+ return cls(id=data["id"], version=data.get("version"), docker_image_artifact=docker_image_artifact, metadata=data.get("metadata", {}))