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,69 @@
1
+ """Verify the A2A agent-config extension actually mutates the served card."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ import uuid
6
+ from typing import ClassVar, Optional
7
+
8
+ import httpx
9
+
10
+ from agent_env.a2a_agent import A2AAgent
11
+ from agent_env.task_step.context import TaskStepContext
12
+ from agent_env.entity_refs import EntityRef
13
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class VerifyA2AAgentConfigIdentityStep(TaskStep):
19
+ type: ClassVar[str] = "verify_a2a_agent_config_identity"
20
+ entity_refs = (EntityRef.agent("a2a_agent_id", version_field="a2a_agent_version"),)
21
+
22
+ def __init__(self, id: str, version: Optional[int], a2a_agent_id: str, a2a_agent_version: Optional[int] = None, depends_on: Optional[list[TaskStepDependency]] = None, fail_task_on_error: bool = True):
23
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
24
+ self.a2a_agent_id = a2a_agent_id
25
+ self.a2a_agent_version = a2a_agent_version
26
+
27
+ def to_dict(self) -> dict:
28
+ return {**super().to_dict(), "a2a_agent_id": self.a2a_agent_id, "a2a_agent_version": self.a2a_agent_version}
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: dict) -> "VerifyA2AAgentConfigIdentityStep":
32
+ return cls(**cls._base_from_dict(data), a2a_agent_id=data["a2a_agent_id"], a2a_agent_version=data.get("a2a_agent_version"))
33
+
34
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
35
+ deployed = next((a for a in context.deployed_agents if a.agent_name == TaskStep.DEFAULT_AGENT_NAME), None)
36
+ if deployed is None:
37
+ raise RuntimeError("VerifyA2AAgentConfigIdentityStep: no deployed default-agent in context")
38
+ a2a_url = deployed.a2a_url or deployed.api_url
39
+ ext = A2AAgent.find_extension(deployed.a2a_card or {}, A2AAgent.EXT_AGENT_CONFIG)
40
+ supported = ((ext or {}).get("params") or {}).get("methods", {}).get("set", {}).get("request", {}).get("supported", [])
41
+ supports_identity = "name" in supported and "description" in supported
42
+
43
+ identity_round_trip_ok = False
44
+ if ext is not None and supports_identity:
45
+ name, desc = f"IdentityName-{uuid.uuid4().hex[:12]}", f"IdentityDesc-{uuid.uuid4().hex[:12]}"
46
+ endpoint = a2a_url + (ext.get("params") or {}).get("endpoint", "/ext/agent-config")
47
+ try:
48
+ async with httpx.AsyncClient() as client:
49
+ (await client.post(endpoint, json={"name": name, "description": desc}, timeout=30)).raise_for_status()
50
+ card_resp = await client.get(f"{a2a_url}/.well-known/agent.json", timeout=30)
51
+ card_resp.raise_for_status()
52
+ card = card_resp.json() or {}
53
+ identity_round_trip_ok = card.get("name") == name and card.get("description") == desc
54
+ except httpx.HTTPError as e:
55
+ logger.warning(f"VerifyA2AAgentConfigIdentityStep: round-trip failed: {e}")
56
+
57
+ entry = {
58
+ "supported": ext is not None and supports_identity and identity_round_trip_ok,
59
+ "extension_advertised": ext is not None,
60
+ "supports_identity": supports_identity,
61
+ "identity_round_trip_ok": identity_round_trip_ok,
62
+ }
63
+ agent = A2AAgent.get(self.a2a_agent_id, self.a2a_agent_version)
64
+ validated_ext = agent.metadata.get("validated_a2a_extensions", {})
65
+ validated_ext[A2AAgent.EXT_AGENT_CONFIG] = entry
66
+ agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext})
67
+ context.metadata.setdefault("verifications", {})["a2a_agent_config_identity"] = entry
68
+ logger.info(f"agent-config identity validation: {entry}")
69
+ return context
@@ -0,0 +1,178 @@
1
+ """Verify A2A agent can invoke MCP tools by sending a test prompt and reading the env trajectory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import time
8
+ import uuid
9
+ from typing import ClassVar, Optional
10
+
11
+ import httpx
12
+
13
+ from agent_env.task_step.context import TaskStepContext
14
+ from agent_env.entity_refs import EntityRef
15
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ TEST_PROMPT = (
20
+ "Use the `search_emails` MCP tool (from the registered MCP server) to search for "
21
+ "emails containing the word 'invoice'. Do not answer from memory and do not call "
22
+ "any other tool — actually invoke `search_emails`. Report the tool's raw output."
23
+ )
24
+
25
+
26
+ class VerifyA2AAgentMCPStep(TaskStep):
27
+ type: ClassVar[str] = "verify_a2a_agent_mcp"
28
+ entity_refs = (EntityRef.agent("a2a_agent_id"),)
29
+
30
+ def __init__(
31
+ self,
32
+ id: str,
33
+ version: Optional[int],
34
+ a2a_agent_id: str,
35
+ timeout_seconds: int = 300,
36
+ poll_interval_seconds: int = 10,
37
+ depends_on: Optional[list[TaskStepDependency]] = None,
38
+ fail_task_on_error: bool = True,
39
+ ):
40
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
41
+ self.a2a_agent_id = a2a_agent_id
42
+ self.timeout_seconds = timeout_seconds
43
+ self.poll_interval_seconds = poll_interval_seconds
44
+
45
+ def to_dict(self) -> dict:
46
+ base = super().to_dict()
47
+ base["a2a_agent_id"] = self.a2a_agent_id
48
+ base["timeout_seconds"] = self.timeout_seconds
49
+ base["poll_interval_seconds"] = self.poll_interval_seconds
50
+ return base
51
+
52
+ @classmethod
53
+ def from_dict(cls, data: dict) -> VerifyA2AAgentMCPStep:
54
+ return cls(**cls._base_from_dict(data), a2a_agent_id=data["a2a_agent_id"], timeout_seconds=data.get("timeout_seconds", 300), poll_interval_seconds=data.get("poll_interval_seconds", 10))
55
+
56
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
57
+ from agent_env.a2a_agent import A2AAgent, protocol
58
+ from agent_env.env.env import require_gateway_url
59
+
60
+ deployed_agent = next((a for a in context.deployed_agents), None)
61
+ if deployed_agent is None:
62
+ raise RuntimeError("No deployed agent found in context")
63
+ deployed_env = next((e for e in context.deployed_envs), None)
64
+ if deployed_env is None:
65
+ raise RuntimeError("No deployed env found in context")
66
+ # It counts tool calls in the gateway's trajectory, so an env without one can't be checked here.
67
+ require_gateway_url(deployed_env, "verify_a2a_agent_mcp")
68
+
69
+ a2a_url = deployed_agent.a2a_url or deployed_agent.api_url
70
+ message_id = uuid.uuid4().hex
71
+
72
+ # Send test prompt via message/send (A2A spec 0.3)
73
+ logger.info(f"Sending test prompt to agent at {a2a_url}...")
74
+ async with httpx.AsyncClient() as client:
75
+ resp = await client.post(f"{a2a_url}/a2a", json={
76
+ "jsonrpc": "2.0", "id": "1", "method": "message/send",
77
+ "params": {"message": {
78
+ "messageId": message_id,
79
+ "role": "user",
80
+ "parts": [{"kind": "text", "text": TEST_PROMPT}],
81
+ }},
82
+ }, timeout=self.timeout_seconds)
83
+ resp.raise_for_status()
84
+ send_body = resp.json()
85
+ if "error" in send_body:
86
+ raise RuntimeError(f"A2A message/send failed: {send_body['error']}")
87
+ send_result = send_body.get("result") or {}
88
+ task_id = send_result["id"]
89
+ logger.info(f"message/send returned task_id={task_id}")
90
+
91
+ # Poll tasks/get until terminal state
92
+ logger.info("Polling for task completion...")
93
+ a2a_result = await self._poll_task(a2a_url, task_id)
94
+ task_status = a2a_result["status"]["state"]
95
+
96
+ # Parse the terminal message through the shared compatibility layer so
97
+ # both legacy top-level telemetry and typed usage telemetry are handled.
98
+ status_msg = (a2a_result.get("status") or {}).get("message") or {}
99
+ terminal = protocol.TerminalResponse.from_message(status_msg)
100
+ agent_response = terminal.response_text
101
+ tool_call_count_reported = terminal.tool_call_count is not None
102
+
103
+ # Read trajectory from env sandbox
104
+ tools_invoked = await self._count_tool_calls(deployed_env.sandbox_id)
105
+ passed = task_status == "completed" and tools_invoked > 0
106
+ logger.info(f"MCP validation: status={task_status} tools_invoked={tools_invoked} passed={passed}")
107
+
108
+ mcp_validation = {
109
+ "passed": passed,
110
+ "tools_invoked": tools_invoked,
111
+ "task_status": task_status,
112
+ "task_id": task_id,
113
+ "agent_response": agent_response[:500],
114
+ }
115
+ context.metadata.setdefault("verifications", {})["a2a_agent_mcp"] = mcp_validation
116
+
117
+ # Build validated mcp-config entry from what we actually tested
118
+ card = deployed_agent.a2a_card or {}
119
+ mcp_ext = A2AAgent.find_extension(card, A2AAgent.EXT_MCP_CONFIG)
120
+ mcp_entry: dict = {"supported": mcp_ext is not None and passed}
121
+ if mcp_ext:
122
+ ext_config = mcp_ext.get("config") or mcp_ext.get("params") or {}
123
+ methods: dict = {}
124
+ for method_name, method_def in ext_config.get("methods", {}).items():
125
+ method_entry: dict = {"supported": passed}
126
+ request = method_def.get("request", {})
127
+ options = []
128
+ for key in ("required", "optional", "supported"):
129
+ options.extend(request.get(key, []))
130
+ for one_of in request.get("oneOf", []):
131
+ options.extend(one_of.get("required", []))
132
+ if options:
133
+ method_entry["options"] = {name: {"supported": passed} for name in options}
134
+ methods[method_name] = method_entry
135
+ mcp_entry["methods"] = methods
136
+
137
+ agent = A2AAgent.get(self.a2a_agent_id)
138
+ validated_ext = agent.metadata.get("validated_a2a_extensions", {})
139
+ validated_ext[A2AAgent.EXT_MCP_CONFIG] = mcp_entry
140
+ validated_data_ext = agent.metadata.get("validated_data_extensions", {})
141
+ validated_data_ext["tool_call_count"] = {"supported": tool_call_count_reported}
142
+ agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext, "validated_data_extensions": validated_data_ext})
143
+
144
+ return context
145
+
146
+ async def _poll_task(self, a2a_url: str, task_id: str) -> dict:
147
+ deadline = time.monotonic() + self.timeout_seconds
148
+ while time.monotonic() < deadline:
149
+ await asyncio.sleep(self.poll_interval_seconds)
150
+ try:
151
+ async with httpx.AsyncClient() as client:
152
+ resp = await client.post(f"{a2a_url}/a2a", json={
153
+ "jsonrpc": "2.0", "id": "poll", "method": "tasks/get",
154
+ "params": {"id": task_id},
155
+ }, timeout=30)
156
+ resp.raise_for_status()
157
+ data = resp.json()
158
+ if "error" in data:
159
+ logger.warning(f"A2A poll error (will retry): {data['error']}")
160
+ continue
161
+ result = data["result"]
162
+ except (httpx.ConnectError, httpx.ReadTimeout, httpx.RemoteProtocolError) as e:
163
+ logger.warning(f"A2A poll failed (will retry): {e}")
164
+ continue
165
+ state = result["status"]["state"]
166
+ if state in ("completed", "failed"):
167
+ return result
168
+ raise TimeoutError(f"A2A task {task_id} did not complete within {self.timeout_seconds}s")
169
+
170
+ @staticmethod
171
+ async def _count_tool_calls(sandbox_id: str) -> int:
172
+ """Read the env gateway trajectory from the gateway container and count tool_call events."""
173
+ from agent_env.providers import get_env_sandbox_provider
174
+ from agent_env.providers import EnvironmentGatewayProvider
175
+ sandbox = await get_env_sandbox_provider().get_sandbox(sandbox_id)
176
+ events = await EnvironmentGatewayProvider().read_trajectory(sandbox)
177
+ count = sum(1 for e in events if e.get("event_type") == "tool_call")
178
+ return count
@@ -0,0 +1,67 @@
1
+ """Verify core A2A protocol support (message/send + tasks/get)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import ClassVar, Optional
7
+
8
+ from agent_env.task_step.context import TaskStepContext
9
+ from agent_env.entity_refs import EntityRef
10
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class VerifyCoreA2AProtocolStep(TaskStep):
16
+ type: ClassVar[str] = "verify_a2a_core_protocol"
17
+ entity_refs = (EntityRef.agent("a2a_agent_id"),)
18
+
19
+ def __init__(
20
+ self,
21
+ id: str,
22
+ version: Optional[int],
23
+ a2a_agent_id: str,
24
+ prompt_id: str,
25
+ depends_on: Optional[list[TaskStepDependency]] = None,
26
+ fail_task_on_error: bool = True,
27
+ ):
28
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
29
+ self.a2a_agent_id = a2a_agent_id
30
+ self.prompt_id = prompt_id
31
+
32
+ def to_dict(self) -> dict:
33
+ base = super().to_dict()
34
+ base["a2a_agent_id"] = self.a2a_agent_id
35
+ base["prompt_id"] = self.prompt_id
36
+ return base
37
+
38
+ @classmethod
39
+ def from_dict(cls, data: dict) -> VerifyCoreA2AProtocolStep:
40
+ return cls(**cls._base_from_dict(data), a2a_agent_id=data["a2a_agent_id"], prompt_id=data["prompt_id"])
41
+
42
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
43
+ from agent_env.a2a_agent import A2AAgent
44
+
45
+ prompt_response = next((pr for pr in context.prompt_responses if pr.prompt_id == self.prompt_id), None)
46
+
47
+ # message/send succeeded if we have a prompt response at all
48
+ message_send_ok = prompt_response is not None
49
+ # tasks/get succeeded if the response has no error_type
50
+ tasks_get_ok = message_send_ok and prompt_response.error_type is None
51
+
52
+ logger.info(f"Core A2A protocol: message/send={message_send_ok} tasks/get={tasks_get_ok}")
53
+ if not message_send_ok:
54
+ logger.error("message/send failed: no prompt response found")
55
+ elif not tasks_get_ok:
56
+ logger.warning(f"tasks/get returned error: {prompt_response.error_type}")
57
+
58
+ protocol = {
59
+ "message/send": {"supported": message_send_ok},
60
+ "tasks/get": {"supported": tasks_get_ok},
61
+ }
62
+
63
+ agent = A2AAgent.get(self.a2a_agent_id)
64
+ agent.update_metadata({**agent.metadata, "validated_a2a_protocol": protocol})
65
+
66
+ context.metadata.setdefault("verifications", {})["a2a_core_protocol"] = protocol
67
+ return context
@@ -0,0 +1,115 @@
1
+ """Verify A2A agent install/v1 extension end-to-end.
2
+
3
+ Pure recorder step. The actual install exercise (deploy sandbox -> run bare task
4
+ container -> InstallAgentTaskStep -> PromptAgent -> RubricsVerifier) lives in the
5
+ chained steps before this one in the validator DAG. This step reads the rubric
6
+ verdict + checks the installed agent's card, then records the outcome on
7
+ `agent.metadata["validated_a2a_extensions"]` and `context.metadata["verifications"]`.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from typing import ClassVar, Optional
14
+
15
+ from agent_env.task_step.context import TaskStepContext
16
+ from agent_env.entity_refs import EntityRef
17
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class VerifyA2AInstallStep(TaskStep):
23
+ type: ClassVar[str] = "verify_a2a_install"
24
+ entity_refs = (EntityRef.agent("a2a_agent_id", version_field="a2a_agent_version"),)
25
+
26
+ def __init__(
27
+ self,
28
+ id: str,
29
+ version: Optional[int],
30
+ a2a_agent_id: str,
31
+ a2a_agent_version: Optional[int] = None,
32
+ agent_name: str = "install-test-agent",
33
+ rubric_verifier_id: Optional[str] = None,
34
+ depends_on: Optional[list[TaskStepDependency]] = None,
35
+ fail_task_on_error: bool = True,
36
+ ):
37
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
38
+ self.a2a_agent_id = a2a_agent_id
39
+ self.a2a_agent_version = a2a_agent_version
40
+ self.agent_name = agent_name
41
+ self.rubric_verifier_id = rubric_verifier_id
42
+
43
+ def to_dict(self) -> dict:
44
+ base = super().to_dict()
45
+ base["a2a_agent_id"] = self.a2a_agent_id
46
+ base["a2a_agent_version"] = self.a2a_agent_version
47
+ base["agent_name"] = self.agent_name
48
+ base["rubric_verifier_id"] = self.rubric_verifier_id
49
+ return base
50
+
51
+ @classmethod
52
+ def from_dict(cls, data: dict) -> VerifyA2AInstallStep:
53
+ return cls(
54
+ **cls._base_from_dict(data),
55
+ a2a_agent_id=data["a2a_agent_id"],
56
+ a2a_agent_version=data.get("a2a_agent_version"),
57
+ agent_name=data.get("agent_name", "install-test-agent"),
58
+ rubric_verifier_id=data.get("rubric_verifier_id"),
59
+ )
60
+
61
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
62
+ from agent_env.a2a_agent import A2AAgent
63
+
64
+ deployed = next(
65
+ (a for a in context.deployed_agents if a.agent_name == self.agent_name),
66
+ None,
67
+ )
68
+ result: dict = {
69
+ "supported": False,
70
+ "advertised": False,
71
+ "installed_successfully": deployed is not None,
72
+ "responded_to_prompt": False,
73
+ }
74
+
75
+ if deployed is None:
76
+ # Install never produced a DeployedAgent — upstream chain failed.
77
+ result["error"] = (
78
+ "InstallAgentTaskStep did not produce a deployed agent named "
79
+ f"{self.agent_name!r} (upstream chain failure); check earlier validator step logs"
80
+ )
81
+ logger.warning(f"[verify_a2a_install] {result['error']}")
82
+ else:
83
+ card = deployed.a2a_card or {}
84
+ result["advertised"] = A2AAgent.find_extension(card, A2AAgent.EXT_INSTALL) is not None
85
+
86
+ if self.rubric_verifier_id:
87
+ rubric = context.metadata.get("verifications", {}).get(self.rubric_verifier_id, {})
88
+ rubric_results = rubric.get("results") or []
89
+ responded = any(
90
+ r.get("id") == "responded" and r.get("result") for r in rubric_results
91
+ )
92
+ result["responded_to_prompt"] = responded
93
+ result["rubric_score"] = rubric.get("score")
94
+ logger.info(
95
+ f"[verify_a2a_install] rubric {self.rubric_verifier_id!r}: "
96
+ f"responded={responded} score={rubric.get('score')}"
97
+ )
98
+
99
+ result["supported"] = result["installed_successfully"] and result["responded_to_prompt"]
100
+
101
+ # Persist on the A2AAgent doc so the hub / next consumers can query it.
102
+ # Guard the DB write: this step runs with fail_task_on_error=False, so a
103
+ # raised exception would be swallowed by the task runner and the result
104
+ # lost. Catch it and always fall through to the in-context write.
105
+ try:
106
+ agent = A2AAgent.get(self.a2a_agent_id, self.a2a_agent_version)
107
+ validated_ext = dict(agent.metadata.get("validated_a2a_extensions", {}))
108
+ validated_ext[A2AAgent.EXT_INSTALL] = result
109
+ agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext})
110
+ except Exception as e: # noqa: BLE001
111
+ logger.warning(f"[verify_a2a_install] failed to persist validated_a2a_extensions: {e}")
112
+
113
+ context.metadata.setdefault("verifications", {})["a2a_install"] = result
114
+ logger.info(f"[verify_a2a_install] result: {result}")
115
+ return context
@@ -0,0 +1,124 @@
1
+ """Verify A2A agent declares LiteLLM cost-attribution support on its card.
2
+
3
+ Static check: reads the agent's `/.well-known/agent.json` (already fetched
4
+ at deploy time and stored on `deployed.a2a_card`) and inspects the
5
+ `urn:agentenv:agent-config/v1` extension. An agent "supports LiteLLM
6
+ attribution" when its agent-config extension advertises **both**
7
+ `project_id` and `task_id` in its `params.methods.set.request.supported`
8
+ list.
9
+
10
+ The check tells consumers (e.g. claude-code's auto-integration flow,
11
+ the hub UI) which agents will honor per-prompt attribution delivered
12
+ through `/ext/agent-config` from `PromptAgentTaskStep`. Agents that
13
+ don't advertise it will have those fields filtered out at the worker
14
+ side with a warning, and spend logs from those agents will not carry
15
+ the `projectId:<id>` / `taskId:<id>` tags.
16
+
17
+ This is a card-level static check only. It does NOT verify the agent
18
+ actually threads attribution into its outbound LLM calls — that's an
19
+ agent-implementation concern (e.g. an agent bridge's own
20
+ `_apply_litellm_attribution` helper). A future runtime probe could be added
21
+ if a self-reporting endpoint is exposed; for now, the static check
22
+ mirrors what an external integrator can discover via the AgentCard.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import logging
28
+ from typing import ClassVar, Optional
29
+
30
+ from agent_env.task_step.context import TaskStepContext
31
+ from agent_env.task_step.task_step import TaskStep, TaskStepDependency
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # Body fields the worker (PromptAgentTaskStep) sends via /ext/agent-config
36
+ # for LiteLLM cost attribution. Both must be in the agent-config extension's
37
+ # `supported` list for the agent to be considered attribution-capable.
38
+ ATTRIBUTION_FIELDS = ("project_id", "task_id")
39
+
40
+
41
+ class VerifyA2ALitellmAttributionStep(TaskStep):
42
+ type: ClassVar[str] = "verify_a2a_litellm_attribution"
43
+
44
+ def __init__(
45
+ self,
46
+ id: str,
47
+ version: Optional[int],
48
+ a2a_agent_id: str,
49
+ a2a_agent_version: Optional[int] = None,
50
+ depends_on: Optional[list[TaskStepDependency]] = None,
51
+ fail_task_on_error: bool = True,
52
+ ):
53
+ super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
54
+ self.a2a_agent_id = a2a_agent_id
55
+ self.a2a_agent_version = a2a_agent_version
56
+
57
+ def to_dict(self) -> dict:
58
+ base = super().to_dict()
59
+ base["a2a_agent_id"] = self.a2a_agent_id
60
+ base["a2a_agent_version"] = self.a2a_agent_version
61
+ return base
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: dict) -> "VerifyA2ALitellmAttributionStep":
65
+ return cls(
66
+ **cls._base_from_dict(data),
67
+ a2a_agent_id=data["a2a_agent_id"],
68
+ a2a_agent_version=data.get("a2a_agent_version"),
69
+ )
70
+
71
+ async def execute(self, context: TaskStepContext) -> TaskStepContext:
72
+ from agent_env.a2a_agent import A2AAgent
73
+
74
+ deployed = next((a for a in context.deployed_agents), None)
75
+ if deployed is None:
76
+ raise RuntimeError("No deployed agent found in context")
77
+
78
+ card = deployed.a2a_card or {}
79
+ agent = A2AAgent.get(self.a2a_agent_id, self.a2a_agent_version)
80
+
81
+ ext = A2AAgent.find_extension(card, A2AAgent.EXT_AGENT_CONFIG)
82
+ supported: list[str] = []
83
+ if ext:
84
+ supported = (
85
+ ((ext.get("params") or {}).get("methods") or {})
86
+ .get("set", {})
87
+ .get("request", {})
88
+ .get("supported")
89
+ or []
90
+ )
91
+
92
+ advertises = {field: field in supported for field in ATTRIBUTION_FIELDS}
93
+ missing = [field for field, present in advertises.items() if not present]
94
+ passed = ext is not None and not missing
95
+
96
+ if not ext:
97
+ logger.warning(
98
+ "Agent '%s' does not advertise the agent-config extension — "
99
+ "LiteLLM attribution cannot flow.", self.a2a_agent_id,
100
+ )
101
+ elif missing:
102
+ logger.warning(
103
+ "Agent '%s' advertises agent-config but is missing attribution "
104
+ "fields in its `supported` set: %s. Spend logs from this agent "
105
+ "will not carry projectId/taskId tags. See an A2A agent bridge's "
106
+ "`_apply_litellm_attribution` helper for a reference implementation.",
107
+ self.a2a_agent_id, missing,
108
+ )
109
+ else:
110
+ logger.info(
111
+ "Agent '%s' supports LiteLLM attribution: advertises %s in "
112
+ "agent-config.supported", self.a2a_agent_id, list(ATTRIBUTION_FIELDS),
113
+ )
114
+
115
+ validated = {
116
+ "passed": passed,
117
+ "extension_present": ext is not None,
118
+ "supported_fields": supported,
119
+ "advertises": advertises,
120
+ "missing": missing,
121
+ }
122
+ agent.update_metadata({**agent.metadata, "validated_litellm_attribution": validated})
123
+ context.metadata.setdefault("verifications", {})["a2a_litellm_attribution"] = validated
124
+ return context