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,268 @@
1
+ """Loading a group's plugins into its registry, and recording each one that did not take effect.
2
+
3
+ Internal: the public contract is ``agent_env.plugins`` and the README section "Register types
4
+ from an installed package". Each registry calls ``merge`` when it is built, so a group's plugins
5
+ are imported then and not before.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import inspect
11
+ import logging
12
+ from collections.abc import Callable, Collection, Iterator
13
+ from dataclasses import dataclass, field
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from agent_env.config import runtime
17
+ from agent_env.plugins import _report, _requirements
18
+ from agent_env.plugins._discovery import Plugin, discover
19
+
20
+ if TYPE_CHECKING:
21
+ from agent_env.config.runtime import Config, PluginFailures
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ ENVS = "agent_env.envs"
26
+ ARTIFACTS = "agent_env.artifacts"
27
+ TASK_STEPS = "agent_env.task_steps"
28
+ SANDBOX_PROVIDERS = "agent_env.sandbox_providers"
29
+ STATE_PROVIDERS = "agent_env.state_providers"
30
+ ENV_PROVIDERS = "agent_env.env_providers"
31
+ EXPLORER_PLUGINS = "agent_env.explorer_plugins"
32
+
33
+
34
+ @dataclass
35
+ class Registrations:
36
+ """The plugin registrations ``merge`` made in one registry, while that registry is built.
37
+
38
+ Only names a plugin still registers remain: config that takes a name over ``release``s it,
39
+ and a check that fails later ``reject``s it.
40
+ """
41
+
42
+ registry: dict[str, Any]
43
+ group: str
44
+ added: dict[str, Plugin]
45
+ classes: dict[str, type]
46
+ failures: dict[str, str] # this group's entry in the Config's PluginFailures
47
+ # The code for each entry in ``failures``.
48
+ codes: dict[str, str] = field(default_factory=dict)
49
+ # Config that took a plugin's name over: name -> (config table, impl as written, the class).
50
+ released: dict[str, tuple[str, str, type]] = field(default_factory=dict)
51
+ # Names more than one entry point claims: left out of the registry, and config cannot take them.
52
+ conflicts: dict[str, tuple[Plugin, ...]] = field(default_factory=dict)
53
+
54
+ @classmethod
55
+ def empty(cls) -> Registrations:
56
+ return cls(registry={}, group="", added={}, classes={}, failures={})
57
+
58
+ def __iter__(self) -> Iterator[str]:
59
+ return iter(list(self.added))
60
+
61
+ def plugin(self, name: str) -> Plugin | None:
62
+ """The plugin that registers ``name``, if one still does."""
63
+ return self.added.get(name)
64
+
65
+ def failed(self, name: str) -> bool:
66
+ """Whether a plugin registering ``name`` was skipped."""
67
+ return name in self.failures
68
+
69
+ def release(self, name: str, table: str, impl: str, replacement: type) -> bool:
70
+ """Config's ``[table]`` takes ``name`` over with ``impl``, which loaded as ``replacement``.
71
+ Returns whether a plugin registered it; warns unless ``replacement`` is the plugin's own class."""
72
+ plugin = self.added.pop(name, None)
73
+ if plugin is None:
74
+ return False
75
+ self.released[name] = (table, impl, replacement)
76
+ if self.classes[name] is not replacement:
77
+ logger.warning("[%s] impl %r replaces the class registered by plugin %s", table, impl, plugin)
78
+ return True
79
+
80
+ def refuse(self, name: str, where: str) -> bool:
81
+ """Whether ``name`` is conflicted, so the config at ``where`` naming it is skipped, with a warning."""
82
+ if name not in self.conflicts:
83
+ return False
84
+ logger.warning("%s names %r and was skipped: %s", where, name, self.failures[name])
85
+ return True
86
+
87
+ def reject(self, name: str, code: str, reason: str) -> None:
88
+ """Drop ``name``'s registration, recording ``reason`` as for a load failure."""
89
+ plugin = self.added.pop(name)
90
+ del self.registry[name]
91
+ self.failures[name] = _reason(plugin, reason)
92
+ self.codes[name] = code
93
+ logger.warning("Plugin %s in %s was skipped: %s", plugin, self.group, reason)
94
+
95
+
96
+ def merge(
97
+ registry: dict[str, Any],
98
+ group: str,
99
+ validate: Callable[[str, Any], type],
100
+ *,
101
+ source: Config | None = None,
102
+ entry: Callable[[type], Any] = lambda cls: cls,
103
+ ) -> Registrations:
104
+ """Add ``group``'s plugins to ``registry``, which holds only the built-ins so far.
105
+
106
+ ``validate(name, loaded)`` returns the class to register or raises to reject it; ``entry``
107
+ turns it into the registry's value. Every skipped plugin is recorded on ``source``, the
108
+ Config the registry is being built for. A name more than one entry point claims is left out,
109
+ and every other name still loads. An ``InventoryProbe`` keeps what this build registered.
110
+ """
111
+ added: dict[str, Plugin] = {}
112
+ classes: dict[str, type] = {}
113
+ failures: dict[str, str] = {}
114
+ codes: dict[str, str] = {}
115
+ conflicts: dict[str, tuple[Plugin, ...]] = {}
116
+ probe = source if isinstance(source, InventoryProbe) else None
117
+ for name, claims in discover(group).items():
118
+ if name in registry:
119
+ claimants = "; ".join(str(p) for p, _ in claims)
120
+ failures[name] = _reason(claimants, "clashes with a built-in")
121
+ codes[name] = _report.BUILTIN_NAME
122
+ logger.warning("Plugin %s in %s clashes with a built-in and was skipped", claimants, group)
123
+ continue
124
+ # A claim whose requirements exclude this agent-env cannot load, so it claims nothing.
125
+ usable, unmet_first = [], None
126
+ for plugin, ep in claims:
127
+ if (unmet := _requirements.incompatibility(getattr(ep, "dist", None))) is None:
128
+ usable.append((plugin, ep))
129
+ continue
130
+ logger.warning("Plugin %s in %s was skipped: it %s", plugin, group, unmet)
131
+ unmet_first = unmet_first or _reason(plugin, unmet)
132
+ if not usable:
133
+ failures[name] = unmet_first
134
+ codes[name] = _report.INCOMPATIBLE_CORE
135
+ continue
136
+ if len(usable) > 1:
137
+ conflicts[name] = tuple(p for p, _ in usable)
138
+ failures[name] = conflict_message(name, group, list(conflicts[name]))
139
+ codes[name] = _report.NAME_CONFLICT
140
+ logger.warning("Plugin name %r in %s was skipped: %s", name, group, failures[name])
141
+ continue
142
+ plugin, ep = usable[0]
143
+ code = _report.LOAD_FAILED
144
+ try:
145
+ loaded = ep.load()
146
+ code = _report.INVALID_PLUGIN
147
+ cls = validate(name, loaded)
148
+ except (Exception, SystemExit) as exc:
149
+ failures[name] = _reason(plugin, f"failed to load: {exc!r}")
150
+ codes[name] = code
151
+ logger.warning("Plugin %s in %s failed to load and was skipped: %r", plugin, group, exc)
152
+ continue
153
+ registry[name] = entry(cls)
154
+ added[name] = plugin
155
+ classes[name] = cls
156
+ if not runtime.in_nested_registry_build():
157
+ _record(source).replace(group, failures)
158
+ registrations = Registrations(
159
+ registry=registry, group=group, added=added, classes=classes, failures=failures, codes=codes,
160
+ conflicts=conflicts,
161
+ )
162
+ if probe is not None:
163
+ probe.registrations[group] = registrations
164
+ return registrations
165
+
166
+
167
+ @dataclass(eq=False)
168
+ class InventoryProbe(runtime.Config):
169
+ """The throwaway Config ``agent_env.plugins.inventory()`` builds registries on: ``merge`` keeps
170
+ each group's Registrations on it."""
171
+
172
+ registrations: dict[str, Registrations] = field(default_factory=dict, repr=False)
173
+
174
+
175
+ def conflict_message(name: str, group: str, claimants: list[Plugin]) -> str:
176
+ """Why ``name`` is left out of ``group``: every claimant, and how to settle it."""
177
+ listed = "; ".join(str(p) for p in claimants)
178
+ first = claimants[0]
179
+ if first.dist and all((p.dist, p.version) == (first.dist, first.version) for p in claimants):
180
+ # Removing the package removes every claim, so only its author can settle it.
181
+ return (
182
+ f"{first.dist} {first.version} registers {name!r} in {group} {len(claimants)} times: {listed}. "
183
+ "Report it to the package's author."
184
+ )
185
+ return (
186
+ f"{len(claimants)} installed plugins register {name!r} in {group}: {listed}. "
187
+ "Remove all but one with `agent-env plugin remove <package>`."
188
+ )
189
+
190
+
191
+ def typed_validator(
192
+ base: type, builtins: Collection[str] = (), required: Collection[str] = ()
193
+ ) -> Callable[[str, Any], type]:
194
+ """A ``merge`` validator for a group whose classes carry a ``type`` ClassVar: a subclass of
195
+ ``base`` with its own ``type``, registered under exactly that name, since documents are
196
+ written and read by it, and implementing what ``unimplemented`` checks."""
197
+
198
+ def validate(name: str, loaded: Any) -> type:
199
+ cls = require_subclass(loaded, base)
200
+ if cls.type == base.type:
201
+ raise TypeError(f"{cls.__qualname__} does not define its own 'type'")
202
+ if cls.type in builtins:
203
+ raise TypeError(f"{cls.__qualname__} inherits the built-in type {cls.type!r}; give it its own type")
204
+ if cls.type != name:
205
+ raise TypeError(f"{cls.__qualname__} has type {cls.type!r}; its entry point must be named {cls.type!r}")
206
+ if problem := unimplemented(cls, base, required):
207
+ raise TypeError(problem)
208
+ return cls
209
+
210
+ return validate
211
+
212
+
213
+ def require_subclass(loaded: Any, base: type) -> type:
214
+ if not (isinstance(loaded, type) and issubclass(loaded, base)):
215
+ raise TypeError(f"{loaded!r} is not a subclass of {base.__name__}")
216
+ return loaded
217
+
218
+
219
+ def unimplemented(cls: type, base: type, required: Collection[str] = ()) -> str | None:
220
+ """What ``cls`` still has to implement to work as a ``base``, if anything: its abstract methods,
221
+ and each of ``required``, a method ``base`` defines only to raise, that it inherits unchanged or
222
+ overrides with a plain function where ``base`` has a classmethod, which is called on the class."""
223
+ missing = set(getattr(cls, "__abstractmethods__", ()))
224
+ unbound = []
225
+ for name in required:
226
+ mine, theirs = inspect.getattr_static(cls, name), inspect.getattr_static(base, name)
227
+ if _function(mine) is _function(theirs):
228
+ missing.add(name)
229
+ elif isinstance(theirs, classmethod) and not isinstance(mine, (classmethod, staticmethod)):
230
+ unbound.append(name)
231
+ problems = []
232
+ if missing:
233
+ names = sorted(missing)
234
+ listed = names[0] if len(names) == 1 else f"{', '.join(names[:-1])} and {names[-1]}"
235
+ problems.append(f"{cls.__qualname__} must implement {listed}")
236
+ problems += [f"{cls.__qualname__}.{name} must be a classmethod" for name in sorted(unbound)]
237
+ return "; ".join(problems) or None
238
+
239
+
240
+ def _function(found: Any) -> Any:
241
+ """``found`` unwrapped from a classmethod or staticmethod."""
242
+ return getattr(found, "__func__", found)
243
+
244
+
245
+ def load_failures(config: Config | None = None) -> dict[str, dict[str, str]]:
246
+ """Every plugin that did not take effect, by group then name, with the reason.
247
+
248
+ A plugin is listed when it failed to load, validate or construct, clashed with a built-in, or
249
+ claims a name another entry point also claims.
250
+ Covers the registries ``config`` (default: the process Config) has built so far, so build
251
+ them first; a deployment that ships its plugins can then assert this is empty at startup.
252
+ ``reset_config()`` starts a new Config, and so a new record.
253
+ """
254
+ return _record(config).snapshot()
255
+
256
+
257
+ def failure_note(group: str, name: str) -> str:
258
+ """``" (…)"`` saying why ``name`` is missing from ``group``'s registry, or ``""``."""
259
+ reason = _record(None).reason(group, name)
260
+ return f" ({reason})" if reason else ""
261
+
262
+
263
+ def _record(config: Config | None) -> PluginFailures:
264
+ return (config or runtime.get_config())._plugin_failures
265
+
266
+
267
+ def _reason(registered_by: object, why: str) -> str:
268
+ return f"registered by {registered_by} but {why}"
@@ -0,0 +1,47 @@
1
+ """The plugin report's format version and reason codes.
2
+
3
+ Internal: the contract is the README section "Plugin report format". A code says why a
4
+ contribution has its status, or why a group, the config or discovery failed. Codes are never
5
+ removed or reused; a new one needs no format bump.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ # The `format_version` of `agent-env plugin list / show / check --json`. Raised only for a change
11
+ # a consumer cannot ignore: a key removed, renamed or retyped, a new status, or a code redefined.
12
+ FORMAT_VERSION = 1
13
+
14
+ LOAD_FAILED = "load-failed"
15
+ INVALID_PLUGIN = "invalid-plugin"
16
+ INCOMPATIBLE_CORE = "incompatible-core"
17
+ BUILTIN_NAME = "builtin-name"
18
+ NAME_CONFLICT = "name-conflict"
19
+ REPLACED_BY_CONFIG = "replaced-by-config"
20
+ ALREADY_ATTACHED = "already-attached"
21
+ NOT_LOADED = "not-loaded"
22
+ STATUS_UNKNOWN = "status-unknown"
23
+ CONFIG_NOT_FOUND = "config-not-found"
24
+ CONFIG_UNREADABLE = "config-unreadable"
25
+ CONFIG_INVALID = "config-invalid"
26
+ GROUP_BUILD_FAILED = "group-build-failed"
27
+ ENTRY_POINTS_UNREADABLE = "entry-points-unreadable"
28
+ QUALIFIED_ONLY = "qualified-only"
29
+
30
+ # Where each code may appear: a contribution status, or "error" for a group, config or discovery error.
31
+ CODES: dict[str, frozenset[str]] = {
32
+ LOAD_FAILED: frozenset({"failed"}),
33
+ INVALID_PLUGIN: frozenset({"failed"}),
34
+ INCOMPATIBLE_CORE: frozenset({"failed"}),
35
+ BUILTIN_NAME: frozenset({"skipped"}),
36
+ NAME_CONFLICT: frozenset({"conflict", "skipped"}),
37
+ REPLACED_BY_CONFIG: frozenset({"replaced"}),
38
+ ALREADY_ATTACHED: frozenset({"active"}),
39
+ NOT_LOADED: frozenset({"unloaded"}),
40
+ STATUS_UNKNOWN: frozenset({"unloaded"}),
41
+ CONFIG_NOT_FOUND: frozenset({"blocked", "error"}),
42
+ CONFIG_UNREADABLE: frozenset({"blocked", "error"}),
43
+ CONFIG_INVALID: frozenset({"blocked", "error"}),
44
+ GROUP_BUILD_FAILED: frozenset({"blocked", "error"}),
45
+ ENTRY_POINTS_UNREADABLE: frozenset({"error"}),
46
+ QUALIFIED_ONLY: frozenset({"active"}),
47
+ }
@@ -0,0 +1,55 @@
1
+ """Whether an installed plugin's requirements admit the agent-env installed next to it.
2
+
3
+ Internal: the contract is the README section "Plugin compatibility". Reads installed metadata only,
4
+ so it runs before a plugin is imported, and for ``inventory(load=False)``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import functools
10
+ from importlib.metadata import PackageNotFoundError, version
11
+ from typing import Any, Optional
12
+
13
+ from packaging.requirements import InvalidRequirement, Requirement
14
+ from packaging.utils import canonicalize_name
15
+
16
+ # What a plugin may constrain: agent-env, and the protocol package it pins exactly.
17
+ CORE = ("agentenv-framework", "agentenv-framework-protocol")
18
+
19
+
20
+ @functools.cache
21
+ def installed(name: str) -> Optional[str]:
22
+ """The installed version of ``name``, or None without metadata, as for a source tree on sys.path."""
23
+ try:
24
+ return version(name)
25
+ except PackageNotFoundError:
26
+ return None
27
+
28
+
29
+ def incompatibility(dist: Any) -> Optional[str]:
30
+ """Why ``dist``'s requirements on agent-env exclude the installed versions, or None.
31
+
32
+ Only unconditional requirements count: one under an extra, or whose marker is false here, does not."""
33
+ try:
34
+ requires = list(getattr(dist, "requires", None) or ())
35
+ except Exception:
36
+ return None # the metadata is reported as unreadable where it is listed
37
+ unmet = []
38
+ for line in requires:
39
+ try:
40
+ requirement = Requirement(line)
41
+ except InvalidRequirement:
42
+ continue
43
+ name = canonicalize_name(requirement.name)
44
+ if name not in CORE or (requirement.marker is not None and not requirement.marker.evaluate({"extra": ""})):
45
+ continue
46
+ have = installed(name)
47
+ if have is None:
48
+ continue
49
+ try:
50
+ met = requirement.specifier.contains(have, prereleases=True)
51
+ except Exception:
52
+ continue
53
+ if not met:
54
+ unmet.append(f"{name}{requirement.specifier} (installed: {have})")
55
+ return f"needs {'; '.join(unmet)}" if unmet else None
@@ -0,0 +1,37 @@
1
+ """Providers for deploying environments."""
2
+
3
+ from agent_env.providers.env_providers import (
4
+ DeployedGateway, EnvironmentGatewayProvider, EnvironmentProvider, EnvironmentServerProvider, MCPServerConfig, WebsiteConfig,
5
+ build_env_provider,
6
+ )
7
+ from agent_env.env.envs.service_db import DB_MCP_PORT, DB_WEB_PORT
8
+ from agent_env.providers.sandbox_providers.sandbox import Sandbox, VmSandbox
9
+ from agent_env.providers.sandbox_providers.sandbox_provider import (
10
+ SANDBOX_MODE_CONTAINER, SANDBOX_MODE_VM,
11
+ SandboxProvider,
12
+ build_sandbox_provider,
13
+ get_sandbox_provider, set_sandbox_provider, reset_sandbox_provider,
14
+ get_env_sandbox_provider, set_env_sandbox_provider, reset_env_sandbox_provider,
15
+ get_agent_sandbox_provider, set_agent_sandbox_provider, reset_agent_sandbox_provider,
16
+ )
17
+ from agent_env.providers.sandbox_providers.chained_sandbox_provider import ChainedSandboxProvider
18
+ from agent_env.providers.sandbox_providers.local_sandbox import LocalSandbox, LocalSandboxProvider
19
+ from agent_env.providers.sandbox_providers.modal_sandbox import ModalSandbox, ModalSandboxProvider
20
+ from agent_env.providers.sandbox_providers.modal_vm_sandbox import ModalVmSandbox, ModalVmSandboxProvider
21
+ from agent_env.providers.sandbox_providers.e2b import E2BSandbox, E2BSandboxProvider
22
+
23
+ __all__ = [
24
+ "ChainedSandboxProvider",
25
+ "DeployedGateway", "EnvironmentGatewayProvider", "EnvironmentProvider", "EnvironmentServerProvider",
26
+ "MCPServerConfig", "DB_WEB_PORT", "DB_MCP_PORT", "Sandbox", "VmSandbox",
27
+ "SANDBOX_MODE_CONTAINER", "SANDBOX_MODE_VM",
28
+ "SandboxProvider", "LocalSandbox", "LocalSandboxProvider",
29
+ "ModalSandbox", "ModalSandboxProvider",
30
+ "ModalVmSandbox", "ModalVmSandboxProvider",
31
+ "E2BSandbox", "E2BSandboxProvider",
32
+ "WebsiteConfig",
33
+ "build_env_provider", "build_sandbox_provider",
34
+ "get_sandbox_provider", "set_sandbox_provider", "reset_sandbox_provider",
35
+ "get_env_sandbox_provider", "set_env_sandbox_provider", "reset_env_sandbox_provider",
36
+ "get_agent_sandbox_provider", "set_agent_sandbox_provider", "reset_agent_sandbox_provider",
37
+ ]
@@ -0,0 +1,12 @@
1
+ """Environment providers: each deploys an env in one topology and hands back its record."""
2
+
3
+ from agent_env.providers.env_providers.env_provider import EnvironmentProvider, build_env_provider
4
+ from agent_env.providers.env_providers.env_gateway_provider import (
5
+ DeployedGateway, EnvironmentGatewayProvider, MCPServerConfig, SidecarConfig, WebsiteConfig,
6
+ )
7
+ from agent_env.providers.env_providers.env_server_provider import EnvironmentServerProvider
8
+
9
+ __all__ = [
10
+ "DeployedGateway", "EnvironmentGatewayProvider", "EnvironmentProvider", "EnvironmentServerProvider", "MCPServerConfig", "SidecarConfig",
11
+ "WebsiteConfig", "build_env_provider",
12
+ ]
@@ -0,0 +1,13 @@
1
+ """The gateway deployment's layout: its compose service name, VM app dir and compose path, and website names.
2
+
3
+ Imports nothing from agent_env, so modules in the ``agent_env.env`` import chain can use these while
4
+ ``agent_env.providers`` is still importing.
5
+ """
6
+
7
+ GATEWAY_SERVICE_NAME = "gateway"
8
+ GATEWAY_APP_DIR = "/app"
9
+
10
+ AGENT_ENV_WEBSITE_BACKEND_PORT = 8000
11
+ AGENT_ENV_WEBSITE_BACKEND_SUFFIX = "website-backend"
12
+ AGENT_ENV_WEBSITE_FRONTEND_SUFFIX = "website-frontend"
13
+ DOCKER_COMPOSE_PATH = f"{GATEWAY_APP_DIR}/docker-compose.yml"