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.
- agent_env/__init__.py +0 -0
- agent_env/a2a_agent/__init__.py +23 -0
- agent_env/a2a_agent/a2a_agent.py +600 -0
- agent_env/a2a_agent/conversation_store.py +356 -0
- agent_env/a2a_agent/object_transfer.py +609 -0
- agent_env/a2a_agent/protocol.py +238 -0
- agent_env/a2a_agent/store.py +201 -0
- agent_env/a2a_agent/validator.py +901 -0
- agent_env/artifact/__init__.py +48 -0
- agent_env/artifact/artifact.py +74 -0
- agent_env/artifact/artifacts/__init__.py +0 -0
- agent_env/artifact/artifacts/cli.py +83 -0
- agent_env/artifact/artifacts/docker_image.py +457 -0
- agent_env/artifact/artifacts/environment.py +89 -0
- agent_env/artifact/artifacts/environment_universe.py +156 -0
- agent_env/artifact/artifacts/file.py +214 -0
- agent_env/artifact/artifacts/file_artifact_universe.py +206 -0
- agent_env/artifact/artifacts/skill.py +206 -0
- agent_env/artifact/artifacts/vm_image.py +95 -0
- agent_env/artifact/ref.py +15 -0
- agent_env/artifact/registry.py +251 -0
- agent_env/artifact/store.py +295 -0
- agent_env/artifact/universe.py +38 -0
- agent_env/attribution.py +41 -0
- agent_env/bundle/__init__.py +4 -0
- agent_env/bundle/_fs.py +128 -0
- agent_env/bundle/authoring.py +239 -0
- agent_env/bundle/installed.py +175 -0
- agent_env/bundle/ledger.py +269 -0
- agent_env/bundle/materialize.py +204 -0
- agent_env/bundle/parse.py +594 -0
- agent_env/bundle/plan.py +414 -0
- agent_env/bundle/resolve.py +404 -0
- agent_env/bundle/run.py +361 -0
- agent_env/cli/__init__.py +69 -0
- agent_env/cli/__main__.py +10 -0
- agent_env/cli/_installers.py +576 -0
- agent_env/cli/_plugin_changes.py +947 -0
- agent_env/cli/a2a_agent/__init__.py +22 -0
- agent_env/cli/a2a_agent/add_skill.py +97 -0
- agent_env/cli/a2a_agent/deploy.py +60 -0
- agent_env/cli/a2a_agent/get.py +15 -0
- agent_env/cli/a2a_agent/get_instance.py +26 -0
- agent_env/cli/a2a_agent/put.py +78 -0
- agent_env/cli/a2a_agent/validate.py +56 -0
- agent_env/cli/artifact/__init__.py +20 -0
- agent_env/cli/artifact/cli.py +83 -0
- agent_env/cli/artifact/environment.py +44 -0
- agent_env/cli/artifact/environment_universe.py +163 -0
- agent_env/cli/artifact/file_artifact_universe.py +281 -0
- agent_env/cli/artifact/skill.py +128 -0
- agent_env/cli/banner.py +25 -0
- agent_env/cli/config.py +263 -0
- agent_env/cli/env/__init__.py +31 -0
- agent_env/cli/env/deploy.py +112 -0
- agent_env/cli/env/gateway.py +57 -0
- agent_env/cli/env/get_instance.py +40 -0
- agent_env/cli/env/mcp_server.py +410 -0
- agent_env/cli/env/multi.py +282 -0
- agent_env/cli/env/service_db.py +99 -0
- agent_env/cli/env/snapshot.py +35 -0
- agent_env/cli/env/state/__init__.py +6 -0
- agent_env/cli/env/state/init.py +108 -0
- agent_env/cli/env/state/teardown.py +44 -0
- agent_env/cli/env/website.py +216 -0
- agent_env/cli/env/website_browser.py +67 -0
- agent_env/cli/eval/__init__.py +16 -0
- agent_env/cli/eval/add_tasks.py +63 -0
- agent_env/cli/eval/create.py +49 -0
- agent_env/cli/eval/run.py +194 -0
- agent_env/cli/identity/__init__.py +5 -0
- agent_env/cli/identity/client_identity.py +37 -0
- agent_env/cli/plugin.py +438 -0
- agent_env/cli/run.py +285 -0
- agent_env/cli/task/__init__.py +21 -0
- agent_env/cli/task/create.py +109 -0
- agent_env/cli/task/get.py +17 -0
- agent_env/cli/task/get_instance.py +34 -0
- agent_env/cli/task/run.py +615 -0
- agent_env/cli/task/validate.py +31 -0
- agent_env/cli/up.py +123 -0
- agent_env/cli/utils.py +183 -0
- agent_env/config/__init__.py +50 -0
- agent_env/config/describe.py +1067 -0
- agent_env/config/errors.py +13 -0
- agent_env/config/loader.py +245 -0
- agent_env/config/model.py +154 -0
- agent_env/config/paths.py +25 -0
- agent_env/config/plugin_tables.py +101 -0
- agent_env/config/provenance.py +134 -0
- agent_env/config/runtime.py +1064 -0
- agent_env/config/snapshot.py +81 -0
- agent_env/entity_refs.py +186 -0
- agent_env/env/__init__.py +49 -0
- agent_env/env/env.py +433 -0
- agent_env/env/env_artifact_store.py +87 -0
- agent_env/env/envs/__init__.py +7 -0
- agent_env/env/envs/_deployment.py +281 -0
- agent_env/env/envs/gateway_server.py +32 -0
- agent_env/env/envs/mcp_server.py +387 -0
- agent_env/env/envs/multi_env.py +780 -0
- agent_env/env/envs/service_db/Dockerfile +20 -0
- agent_env/env/envs/service_db/Dockerfile.db-mcp +42 -0
- agent_env/env/envs/service_db/Dockerfile.db-web +8 -0
- agent_env/env/envs/service_db.py +92 -0
- agent_env/env/envs/website.py +294 -0
- agent_env/env/envs/website_browser/Dockerfile +40 -0
- agent_env/env/envs/website_browser/__init__.py +17 -0
- agent_env/env/envs/website_browser/entrypoint.sh +13 -0
- agent_env/env/gateway/Dockerfile +14 -0
- agent_env/env/gateway/__init__.py +37 -0
- agent_env/env/gateway/clock.py +184 -0
- agent_env/env/gateway/constants.py +190 -0
- agent_env/env/gateway/entrypoint.py +156 -0
- agent_env/env/gateway/gateway.py +1095 -0
- agent_env/env/gateway/get_time.py +60 -0
- agent_env/env/gateway/requirements.txt +4 -0
- agent_env/env/gateway/triggers.py +1193 -0
- agent_env/env/legacy_protocol.py +146 -0
- agent_env/env/registry.py +93 -0
- agent_env/env/snapshot_store.py +372 -0
- agent_env/env/store.py +411 -0
- agent_env/eval/__init__.py +14 -0
- agent_env/eval/eval.py +68 -0
- agent_env/eval/store.py +116 -0
- agent_env/examples/hello/README.md +9 -0
- agent_env/examples/hello/artifacts/greeting/check.sh +1 -0
- agent_env/examples/hello/artifacts/greeting/hello.txt +1 -0
- agent_env/examples/hello/tasks/hello.json +10 -0
- agent_env/explorer/__init__.py +12 -0
- agent_env/explorer/app.py +277 -0
- agent_env/explorer/openapi_docs.py +242 -0
- agent_env/explorer/plugin.py +95 -0
- agent_env/explorer/routers/__init__.py +1 -0
- agent_env/explorer/routers/common.py +174 -0
- agent_env/explorer/routers/conversations.py +25 -0
- agent_env/explorer/routers/objects.py +112 -0
- agent_env/explorer/routers/runs.py +475 -0
- agent_env/plugins/__init__.py +59 -0
- agent_env/plugins/_cli.py +244 -0
- agent_env/plugins/_discovery.py +97 -0
- agent_env/plugins/_inventory.py +262 -0
- agent_env/plugins/_registration.py +268 -0
- agent_env/plugins/_report.py +47 -0
- agent_env/plugins/_requirements.py +55 -0
- agent_env/providers/__init__.py +37 -0
- agent_env/providers/env_providers/__init__.py +12 -0
- agent_env/providers/env_providers/constants.py +13 -0
- agent_env/providers/env_providers/env_gateway_provider.py +1354 -0
- agent_env/providers/env_providers/env_provider.py +237 -0
- agent_env/providers/env_providers/env_server_provider.py +133 -0
- agent_env/providers/env_state/__init__.py +49 -0
- agent_env/providers/env_state/env_state_provider.py +554 -0
- agent_env/providers/env_state/local_postgres.py +390 -0
- agent_env/providers/env_state/store.py +112 -0
- agent_env/providers/sandbox_providers/__init__.py +29 -0
- agent_env/providers/sandbox_providers/chained_sandbox_provider.py +93 -0
- agent_env/providers/sandbox_providers/e2b/__init__.py +6 -0
- agent_env/providers/sandbox_providers/e2b/provider.py +376 -0
- agent_env/providers/sandbox_providers/e2b/sandbox.py +342 -0
- agent_env/providers/sandbox_providers/e2b/template.py +158 -0
- agent_env/providers/sandbox_providers/local_sandbox.py +408 -0
- agent_env/providers/sandbox_providers/modal_sandbox.py +500 -0
- agent_env/providers/sandbox_providers/modal_vm_sandbox.py +466 -0
- agent_env/providers/sandbox_providers/sandbox.py +387 -0
- agent_env/providers/sandbox_providers/sandbox_provider.py +614 -0
- agent_env/py.typed +0 -0
- agent_env/runner/__init__.py +16 -0
- agent_env/runner/local_runner.py +190 -0
- agent_env/runner/runner.py +127 -0
- agent_env/runner/store.py +94 -0
- agent_env/store/__init__.py +126 -0
- agent_env/store/_google.py +68 -0
- agent_env/store/base.py +33 -0
- agent_env/store/document_store/__init__.py +60 -0
- agent_env/store/document_store/document_store.py +519 -0
- agent_env/store/document_store/dynamodb_document_store.py +306 -0
- agent_env/store/document_store/evaluation.py +199 -0
- agent_env/store/document_store/firestore_mongo_document_store.py +242 -0
- agent_env/store/document_store/mongo_document_store.py +259 -0
- agent_env/store/document_store/sqlite_document_store.py +314 -0
- agent_env/store/ids.py +123 -0
- agent_env/store/image_store/__init__.py +34 -0
- agent_env/store/image_store/ecr_image_store.py +131 -0
- agent_env/store/image_store/google_credentials.py +113 -0
- agent_env/store/image_store/image_store.py +113 -0
- agent_env/store/image_store/local_registry_image_store.py +106 -0
- agent_env/store/image_store/oci_registry_credentials.py +196 -0
- agent_env/store/local_state.py +27 -0
- agent_env/store/object_store/__init__.py +24 -0
- agent_env/store/object_store/gcs_object_store.py +415 -0
- agent_env/store/object_store/local_object_store.py +143 -0
- agent_env/store/object_store/object_store.py +198 -0
- agent_env/store/object_store/s3_object_store.py +354 -0
- agent_env/store/query.py +112 -0
- agent_env/store/routing.py +609 -0
- agent_env/store/secret_store/__init__.py +19 -0
- agent_env/store/secret_store/aws_secrets_manager_secret_store.py +293 -0
- agent_env/store/secret_store/gcp_secret_manager_secret_store.py +319 -0
- agent_env/store/secret_store/local_secret_store.py +47 -0
- agent_env/store/secret_store/secret_store.py +19 -0
- agent_env/task/__init__.py +27 -0
- agent_env/task/interrupts.py +109 -0
- agent_env/task/registry.py +27 -0
- agent_env/task/step_journal.py +99 -0
- agent_env/task/store.py +940 -0
- agent_env/task/task.py +815 -0
- agent_env/task/teardown.py +142 -0
- agent_env/task_step/__init__.py +47 -0
- agent_env/task_step/context.py +195 -0
- agent_env/task_step/context_ops.py +224 -0
- agent_env/task_step/registry.py +182 -0
- agent_env/task_step/review_store.py +118 -0
- agent_env/task_step/snapshot_utils/__init__.py +2 -0
- agent_env/task_step/snapshot_utils/agent_state_capture.py +261 -0
- agent_env/task_step/snapshot_utils/snapshot_series.py +631 -0
- agent_env/task_step/store.py +129 -0
- agent_env/task_step/task_step.py +173 -0
- agent_env/task_step/task_steps/__init__.py +27 -0
- agent_env/task_step/task_steps/a2a_agent_validator/__init__.py +31 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/README.md +158 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.m4a +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp3 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.mp4 +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.ogg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/clip.wav +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/document.pdf +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.gif +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.jpg +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/fixtures/red.png +0 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_card.py +103 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_config_identity.py +69 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_agent_mcp.py +178 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_core_protocol.py +67 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_install.py +115 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution.py +124 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_litellm_attribution_runtime.py +173 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_modalities.py +270 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_peer_agents.py +102 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_role.py +151 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_skill_config.py +194 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_snapshot.py +111 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_system_prompt.py +215 -0
- agent_env/task_step/task_steps/a2a_agent_validator/verify_a2a_trajectory.py +164 -0
- agent_env/task_step/task_steps/add_skills.py +240 -0
- agent_env/task_step/task_steps/apply_server_config.py +288 -0
- agent_env/task_step/task_steps/collect_artifacts.py +940 -0
- agent_env/task_step/task_steps/deploy_agent.py +626 -0
- agent_env/task_step/task_steps/deploy_env.py +197 -0
- agent_env/task_step/task_steps/deploy_human_agent.py +100 -0
- agent_env/task_step/task_steps/deploy_sandbox.py +170 -0
- agent_env/task_step/task_steps/env_card_validator/__init__.py +4 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_card.py +109 -0
- agent_env/task_step/task_steps/env_card_validator/verify_env_core_protocol.py +108 -0
- agent_env/task_step/task_steps/install_agent.py +376 -0
- agent_env/task_step/task_steps/load_artifact.py +775 -0
- agent_env/task_step/task_steps/mcp_cli_builder/__init__.py +4 -0
- agent_env/task_step/task_steps/mcp_cli_builder/build_mcp_cli.py +201 -0
- agent_env/task_step/task_steps/mcp_cli_builder/codegen.py +627 -0
- agent_env/task_step/task_steps/mcp_env_validator/__init__.py +44 -0
- agent_env/task_step/task_steps/mcp_env_validator/validation_gate_aggregator.py +102 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_env_assessment.py +78 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_mcp_tool_schema.py +152 -0
- agent_env/task_step/task_steps/mcp_env_validator/verify_spec_conformance.py +486 -0
- agent_env/task_step/task_steps/modify_env_tool_access.py +80 -0
- agent_env/task_step/task_steps/multienv_validator/__init__.py +3 -0
- agent_env/task_step/task_steps/multienv_validator/combine_universe_verdicts.py +107 -0
- agent_env/task_step/task_steps/multienv_validator/universe_comparison.py +253 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_agent_judge.py +180 -0
- agent_env/task_step/task_steps/multienv_validator/verify_universe_roundtrip.py +241 -0
- agent_env/task_step/task_steps/peer_agents.py +92 -0
- agent_env/task_step/task_steps/prompt_agent.py +900 -0
- agent_env/task_step/task_steps/register_agent_triggers.py +113 -0
- agent_env/task_step/task_steps/register_env_triggers.py +97 -0
- agent_env/task_step/task_steps/reset_env.py +71 -0
- agent_env/task_step/task_steps/review.py +152 -0
- agent_env/task_step/task_steps/run_code.py +461 -0
- agent_env/task_step/task_steps/run_code_runner.py +52 -0
- agent_env/task_step/task_steps/run_docker_container.py +380 -0
- agent_env/task_step/task_steps/sandbox_utils/__init__.py +0 -0
- agent_env/task_step/task_steps/sandbox_utils/sandbox_utils.py +79 -0
- agent_env/task_step/task_steps/snapshot_agent_state.py +205 -0
- agent_env/task_step/task_steps/snapshot_env.py +637 -0
- agent_env/task_step/task_steps/sync_env_clock.py +132 -0
- agent_env/task_step/task_steps/teardown_sandboxes.py +170 -0
- agent_env/task_step/task_steps/verifiers/__init__.py +23 -0
- agent_env/task_step/task_steps/verifiers/agent_prompt_response_verifier.py +166 -0
- agent_env/task_step/task_steps/verifiers/aggregate_verifiers.py +90 -0
- agent_env/task_step/task_steps/verifiers/env_outcome_verifier.py +127 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/__init__.py +2 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/frame_selection.py +377 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/judge_output_format.py +1293 -0
- agent_env/task_step/task_steps/verifiers/judge_utils/trajectory_filter.py +514 -0
- agent_env/task_step/task_steps/verifiers/rubrics_verifier.py +1318 -0
- agent_env/task_step/task_steps/verifiers/run_container_unit_tests_verifier.py +434 -0
- agent_env/task_step/task_steps/verifiers/scoring.py +40 -0
- agent_env/task_step/task_steps/verifiers/verify_sandbox.py +308 -0
- agent_env/task_step/thread_work.py +61 -0
- agent_env/utils/__init__.py +0 -0
- agent_env/utils/card_naming.py +108 -0
- agent_env/utils/deprecation.py +109 -0
- agent_env/utils/docker_build.py +29 -0
- agent_env/utils/exec_retry.py +77 -0
- agent_env/utils/litellm_attribution.py +59 -0
- agent_env/utils/paths.py +20 -0
- agentenv_framework-0.9.1254.dist-info/METADATA +394 -0
- agentenv_framework-0.9.1254.dist-info/RECORD +312 -0
- agentenv_framework-0.9.1254.dist-info/WHEEL +4 -0
- agentenv_framework-0.9.1254.dist-info/entry_points.txt +5 -0
- agentenv_framework-0.9.1254.dist-info/licenses/LICENSE +202 -0
- agentenv_framework-0.9.1254.dist-info/licenses/NOTICE +4 -0
- agentenv_framework-0.9.1254.dist-info/licenses/THIRD_PARTY_NOTICES.md +7665 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Verify the advertised A2A trajectory retrieval variants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import ClassVar, Optional
|
|
8
|
+
|
|
9
|
+
from agent_env.task_step.context import TaskStepContext
|
|
10
|
+
from agent_env.a2a_agent.object_transfer import (
|
|
11
|
+
REPLY_TIMEOUT_SECONDS,
|
|
12
|
+
TransferCall,
|
|
13
|
+
TrajectoryUpload,
|
|
14
|
+
choose_transfer,
|
|
15
|
+
fetch_trajectory,
|
|
16
|
+
invoke_transfer,
|
|
17
|
+
trajectory_mode,
|
|
18
|
+
)
|
|
19
|
+
from agent_env.task_step.snapshot_utils.agent_state_capture import trajectory_object_url
|
|
20
|
+
from agent_env.entity_refs import EntityRef
|
|
21
|
+
from agent_env.task_step.task_step import TaskStep, TaskStepDependency
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VerifyA2ATrajectoryStep(TaskStep):
|
|
27
|
+
type: ClassVar[str] = "verify_a2a_trajectory"
|
|
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
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
36
|
+
fail_task_on_error: bool = True,
|
|
37
|
+
):
|
|
38
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
39
|
+
self.a2a_agent_id = a2a_agent_id
|
|
40
|
+
|
|
41
|
+
def to_dict(self) -> dict:
|
|
42
|
+
base = super().to_dict()
|
|
43
|
+
base["a2a_agent_id"] = self.a2a_agent_id
|
|
44
|
+
return base
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_dict(cls, data: dict) -> VerifyA2ATrajectoryStep:
|
|
48
|
+
return cls(**cls._base_from_dict(data), a2a_agent_id=data["a2a_agent_id"])
|
|
49
|
+
|
|
50
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
51
|
+
from agent_env.a2a_agent import A2AAgent
|
|
52
|
+
from agent_env.config import get_config
|
|
53
|
+
|
|
54
|
+
deployed_agent = next((a for a in context.deployed_agents), None)
|
|
55
|
+
if deployed_agent is None:
|
|
56
|
+
raise RuntimeError("No deployed agent found in context")
|
|
57
|
+
|
|
58
|
+
card = deployed_agent.a2a_card or {}
|
|
59
|
+
traj_ext = A2AAgent.find_extension(card, A2AAgent.EXT_TRAJECTORY)
|
|
60
|
+
if not traj_ext:
|
|
61
|
+
logger.warning("Trajectory extension not advertised in agent card")
|
|
62
|
+
agent = A2AAgent.get(self.a2a_agent_id)
|
|
63
|
+
validated_ext = agent.metadata.get("validated_a2a_extensions", {})
|
|
64
|
+
validated_ext[A2AAgent.EXT_TRAJECTORY] = {"supported": False}
|
|
65
|
+
agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext})
|
|
66
|
+
context.metadata.setdefault("verifications", {})["a2a_trajectory"] = {"supported": False}
|
|
67
|
+
return context
|
|
68
|
+
|
|
69
|
+
a2a_url = deployed_agent.a2a_url or deployed_agent.api_url
|
|
70
|
+
get_method, get_path = A2AAgent.operation(traj_ext, "get")
|
|
71
|
+
endpoint = a2a_url + get_path
|
|
72
|
+
|
|
73
|
+
# Get task_id from the MCP step
|
|
74
|
+
mcp_result = context.metadata.get("verifications", {}).get("a2a_agent_mcp", {})
|
|
75
|
+
task_id = mcp_result.get("task_id")
|
|
76
|
+
if not task_id:
|
|
77
|
+
raise RuntimeError("No task_id from MCP verification step — trajectory validation requires a completed task")
|
|
78
|
+
|
|
79
|
+
# Test inline trajectory retrieval
|
|
80
|
+
inline_ok = False
|
|
81
|
+
try:
|
|
82
|
+
fetched = await fetch_trajectory(endpoint, {"task_id": task_id})
|
|
83
|
+
if fetched.inline:
|
|
84
|
+
inline_ok = True
|
|
85
|
+
logger.info(f"Inline trajectory: OK ({len(fetched.inline)} events)")
|
|
86
|
+
else:
|
|
87
|
+
logger.warning("Inline trajectory: response missing 'trajectory' key or empty")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.warning(f"Inline trajectory: failed ({e})")
|
|
90
|
+
|
|
91
|
+
# The object variant is probed only on a store that can issue its grant.
|
|
92
|
+
objects_ok = False
|
|
93
|
+
config = get_config()
|
|
94
|
+
store = config.get_object_store()
|
|
95
|
+
probe_prefix = f"{config.get_artifact_key_prefix()}a2a_validator_trajectories/{self.a2a_agent_id}/"
|
|
96
|
+
if trajectory_mode(get_method, store, by="task_id") == "objects":
|
|
97
|
+
try:
|
|
98
|
+
prefix = store.object_url(probe_prefix)
|
|
99
|
+
upload = await asyncio.to_thread(
|
|
100
|
+
TrajectoryUpload.to, store, trajectory_object_url(prefix, name=task_id, store=store)
|
|
101
|
+
)
|
|
102
|
+
await fetch_trajectory(endpoint, {"task_id": task_id}, upload=upload)
|
|
103
|
+
objects_ok = True
|
|
104
|
+
logger.info("Object trajectory: OK")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.warning(f"Object trajectory: failed ({e})")
|
|
107
|
+
|
|
108
|
+
s3_ok = False
|
|
109
|
+
if choose_transfer(
|
|
110
|
+
get_method, legacy=("task_id", "trajectory_s3_prefix"), store=store
|
|
111
|
+
) == "legacy":
|
|
112
|
+
s3_prefix = store.object_url(probe_prefix)
|
|
113
|
+
try:
|
|
114
|
+
data = await invoke_transfer(
|
|
115
|
+
endpoint,
|
|
116
|
+
TransferCall(
|
|
117
|
+
"legacy", {"task_id": task_id, "trajectory_s3_prefix": s3_prefix}
|
|
118
|
+
),
|
|
119
|
+
verb="POST",
|
|
120
|
+
operation="legacy trajectory get",
|
|
121
|
+
timeout=REPLY_TIMEOUT_SECONDS,
|
|
122
|
+
)
|
|
123
|
+
if data.get("trajectory_s3_prefix"):
|
|
124
|
+
s3_ok = True
|
|
125
|
+
logger.info(f"S3 trajectory: OK ({data['trajectory_s3_prefix']})")
|
|
126
|
+
else:
|
|
127
|
+
logger.warning(
|
|
128
|
+
"S3 trajectory: response missing 'trajectory_s3_prefix' key or empty"
|
|
129
|
+
)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.warning(f"S3 trajectory: failed ({e})")
|
|
132
|
+
|
|
133
|
+
logger.info(
|
|
134
|
+
f"Trajectory validation: inline={inline_ok} objects={objects_ok} s3={s3_ok}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Build validated_a2a_extensions entry
|
|
138
|
+
traj_entry = {
|
|
139
|
+
"supported": inline_ok or objects_ok or s3_ok,
|
|
140
|
+
"methods": {
|
|
141
|
+
"get": {
|
|
142
|
+
"supported": inline_ok or objects_ok or s3_ok,
|
|
143
|
+
"options": {
|
|
144
|
+
"task_id": {
|
|
145
|
+
"supported": inline_ok or objects_ok or s3_ok
|
|
146
|
+
},
|
|
147
|
+
"objects": {"supported": objects_ok},
|
|
148
|
+
"trajectory_s3_prefix": {"supported": s3_ok},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
agent = A2AAgent.get(self.a2a_agent_id)
|
|
155
|
+
validated_ext = agent.metadata.get("validated_a2a_extensions", {})
|
|
156
|
+
validated_ext[A2AAgent.EXT_TRAJECTORY] = traj_entry
|
|
157
|
+
agent.update_metadata({**agent.metadata, "validated_a2a_extensions": validated_ext})
|
|
158
|
+
|
|
159
|
+
context.metadata.setdefault("verifications", {})["a2a_trajectory"] = {
|
|
160
|
+
"inline": inline_ok,
|
|
161
|
+
"objects": objects_ok,
|
|
162
|
+
"s3": s3_ok,
|
|
163
|
+
}
|
|
164
|
+
return context
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Add skills to a deployed A2A agent."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import ClassVar, Optional
|
|
9
|
+
|
|
10
|
+
from agent_env.task_step.context import TaskStepContext
|
|
11
|
+
from agent_env.entity_refs import EntityRef
|
|
12
|
+
from agent_env.task_step.task_step import TaskStep, TaskStepDependency
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Skill:
|
|
19
|
+
"""Represents an Agent Skill following the Agent Skills specification.
|
|
20
|
+
|
|
21
|
+
See https://agentskills.io/specification for the full spec.
|
|
22
|
+
|
|
23
|
+
A skill can come from three mutually-exclusive sources:
|
|
24
|
+
- **inline**: body + frontmatter fields, rendered into SKILL.md at send time
|
|
25
|
+
- **s3_url**: S3 prefix containing a skill directory with its own SKILL.md
|
|
26
|
+
- **skill_artifact_id**: a SkillArtifact in the store; name/description/skill_s3_url
|
|
27
|
+
are resolved from it at send time
|
|
28
|
+
"""
|
|
29
|
+
name: Optional[str] = None
|
|
30
|
+
description: Optional[str] = None
|
|
31
|
+
body: Optional[str] = None
|
|
32
|
+
license: Optional[str] = None
|
|
33
|
+
compatibility: Optional[str] = None
|
|
34
|
+
metadata: Optional[dict[str, str]] = None
|
|
35
|
+
allowed_tools: Optional[str] = None
|
|
36
|
+
s3_url: Optional[str] = None
|
|
37
|
+
skill_artifact_id: Optional[str] = None
|
|
38
|
+
skill_artifact_version: Optional[int] = None
|
|
39
|
+
|
|
40
|
+
def __post_init__(self):
|
|
41
|
+
modes = [bool(self.body), bool(self.s3_url), bool(self.skill_artifact_id)]
|
|
42
|
+
if sum(modes) != 1:
|
|
43
|
+
raise ValueError("Skill must have exactly one of: body (inline), s3_url, skill_artifact_id")
|
|
44
|
+
if self.skill_artifact_id is not None:
|
|
45
|
+
if any([self.body, self.s3_url, self.license, self.compatibility, self.metadata, self.allowed_tools]):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"Skill with skill_artifact_id must not have body/s3_url/license/compatibility/metadata/allowed_tools"
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
if not self.name or not self.description:
|
|
51
|
+
raise ValueError("Inline or s3_url skills require name and description")
|
|
52
|
+
if self.s3_url and any([self.body, self.license, self.compatibility, self.metadata, self.allowed_tools]):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"Skill with s3_url must not have body/license/compatibility/metadata/allowed_tools — "
|
|
55
|
+
"the S3 skill directory has its own SKILL.md"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def validate(self) -> None:
|
|
59
|
+
from agent_env.artifact import SkillArtifact
|
|
60
|
+
|
|
61
|
+
if self.skill_artifact_id is not None:
|
|
62
|
+
return
|
|
63
|
+
if self.s3_url is not None:
|
|
64
|
+
SkillArtifact.validate(s3_url=self.s3_url, expected_name=self.name)
|
|
65
|
+
return
|
|
66
|
+
SkillArtifact.validate(skill_md=self.to_skill_md().encode("utf-8"), expected_name=self.name)
|
|
67
|
+
|
|
68
|
+
def to_skill_md(self) -> str:
|
|
69
|
+
"""Render the full SKILL.md content with YAML frontmatter + body."""
|
|
70
|
+
lines = ["---", f"name: {self.name}", f"description: {self.description}"]
|
|
71
|
+
if self.license:
|
|
72
|
+
lines.append(f"license: {self.license}")
|
|
73
|
+
if self.compatibility:
|
|
74
|
+
lines.append(f"compatibility: {self.compatibility}")
|
|
75
|
+
if self.metadata:
|
|
76
|
+
lines.append("metadata:")
|
|
77
|
+
for k, v in self.metadata.items():
|
|
78
|
+
lines.append(f' {k}: "{v}"')
|
|
79
|
+
if self.allowed_tools:
|
|
80
|
+
lines.append(f"allowed-tools: {self.allowed_tools}")
|
|
81
|
+
lines.append("---")
|
|
82
|
+
if self.body:
|
|
83
|
+
lines.append("")
|
|
84
|
+
lines.append(self.body)
|
|
85
|
+
return "\n".join(lines)
|
|
86
|
+
|
|
87
|
+
def to_dict(self) -> dict:
|
|
88
|
+
d: dict = {}
|
|
89
|
+
for field_name in (
|
|
90
|
+
"name", "description", "body", "license", "compatibility",
|
|
91
|
+
"metadata", "allowed_tools", "s3_url",
|
|
92
|
+
"skill_artifact_id", "skill_artifact_version",
|
|
93
|
+
):
|
|
94
|
+
value = getattr(self, field_name)
|
|
95
|
+
if value is not None:
|
|
96
|
+
d[field_name] = value
|
|
97
|
+
return d
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_dict(cls, data: dict) -> Skill:
|
|
101
|
+
return cls(
|
|
102
|
+
name=data.get("name"), description=data.get("description"),
|
|
103
|
+
body=data.get("body"), license=data.get("license"),
|
|
104
|
+
compatibility=data.get("compatibility"), metadata=data.get("metadata"),
|
|
105
|
+
allowed_tools=data.get("allowed_tools"), s3_url=data.get("s3_url"),
|
|
106
|
+
skill_artifact_id=data.get("skill_artifact_id"),
|
|
107
|
+
skill_artifact_version=data.get("skill_artifact_version"),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _sanitize_skill_name(s: str) -> str:
|
|
113
|
+
"""Coerce arbitrary text into the Agent Skills name regex
|
|
114
|
+
(^(?!-)(?!.*--)[a-z0-9-]{1,64}(?<!-)$). Empty inputs raise."""
|
|
115
|
+
s = s.lower()
|
|
116
|
+
s = re.sub(r"[^a-z0-9-]+", "-", s) # replace runs of disallowed chars with a hyphen
|
|
117
|
+
s = re.sub(r"-+", "-", s) # collapse consecutive hyphens
|
|
118
|
+
s = s.strip("-") # drop leading/trailing hyphens
|
|
119
|
+
if not s:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"Cannot derive a valid Agent Skills name from input: result was empty after sanitization"
|
|
122
|
+
)
|
|
123
|
+
return s[:60].rstrip("-")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _build_skill_for_installed_cli(cli_artifact_id: str, entry: dict) -> Skill:
|
|
127
|
+
command_name = entry["command_name"]
|
|
128
|
+
install_path = entry["install_path"]
|
|
129
|
+
skill_name = f"{_sanitize_skill_name(command_name)}-cli"
|
|
130
|
+
return Skill(
|
|
131
|
+
name=skill_name,
|
|
132
|
+
description=(
|
|
133
|
+
f"Use when working with the {command_name} environment. "
|
|
134
|
+
f"Run with --help to discover available subcommands at runtime."
|
|
135
|
+
),
|
|
136
|
+
body=(
|
|
137
|
+
f"# {command_name} CLI\n\n"
|
|
138
|
+
f"A command-line interface for the `{command_name}` MCP environment.\n\n"
|
|
139
|
+
f"## Location\n\nThe binary is installed at `{install_path}`.\n\n"
|
|
140
|
+
f"## Discovery\n\n"
|
|
141
|
+
f"Run `{install_path} --help` to see all currently-available subcommands. "
|
|
142
|
+
f"Run `{install_path} <subcommand> --help` for the flags of a specific subcommand.\n\n"
|
|
143
|
+
f"## Output\n\nSubcommands print pretty-printed JSON on success. Errors go to stderr with non-zero exit code.\n"
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _build_skill_for_loaded_file_artifact_universe(universe_id: str, entry: dict) -> Skill:
|
|
149
|
+
destination_path = entry["destination_path"]
|
|
150
|
+
return Skill(
|
|
151
|
+
name=f"{universe_id}-files",
|
|
152
|
+
description=f"Files possibly relevant to the current task are available at {destination_path}.",
|
|
153
|
+
body=f"Files possibly relevant to the current task are available at `{destination_path}`.\n",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AddSkillsTaskStep(TaskStep):
|
|
158
|
+
type: ClassVar[str] = "add_skills"
|
|
159
|
+
entity_refs = (
|
|
160
|
+
EntityRef.artifact("skills[].skill_artifact_id", version_field="skill_artifact_version", artifact_type="skill"),
|
|
161
|
+
EntityRef.artifact("cli_artifact_ids[]", artifact_type="cli"),
|
|
162
|
+
EntityRef.artifact("file_artifact_universe_ids[]"),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
id: str,
|
|
168
|
+
version: Optional[int],
|
|
169
|
+
skills: Optional[list[Skill | dict]] = None,
|
|
170
|
+
cli_artifact_ids: Optional[list[str]] = None,
|
|
171
|
+
file_artifact_universe_ids: Optional[list[str]] = None,
|
|
172
|
+
agent_name: Optional[str] = None,
|
|
173
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
174
|
+
fail_task_on_error: bool = True,
|
|
175
|
+
):
|
|
176
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
177
|
+
self.skills = [s if isinstance(s, Skill) else Skill.from_dict(s) for s in (skills or [])]
|
|
178
|
+
self.cli_artifact_ids = list(cli_artifact_ids or [])
|
|
179
|
+
self.file_artifact_universe_ids = list(file_artifact_universe_ids or [])
|
|
180
|
+
self.agent_name = agent_name or TaskStep.DEFAULT_AGENT_NAME
|
|
181
|
+
|
|
182
|
+
def to_dict(self) -> dict:
|
|
183
|
+
base = super().to_dict()
|
|
184
|
+
base["skills"] = [s.to_dict() for s in self.skills]
|
|
185
|
+
base["cli_artifact_ids"] = self.cli_artifact_ids
|
|
186
|
+
base["file_artifact_universe_ids"] = self.file_artifact_universe_ids
|
|
187
|
+
base["agent_name"] = self.agent_name
|
|
188
|
+
return base
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def from_dict(cls, data: dict) -> AddSkillsTaskStep:
|
|
192
|
+
return cls(
|
|
193
|
+
**cls._base_from_dict(data),
|
|
194
|
+
skills=data.get("skills"),
|
|
195
|
+
cli_artifact_ids=data.get("cli_artifact_ids"),
|
|
196
|
+
file_artifact_universe_ids=data.get("file_artifact_universe_ids"),
|
|
197
|
+
agent_name=data.get("agent_name"),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
201
|
+
from agent_env.a2a_agent import A2AAgent
|
|
202
|
+
from agent_env.a2a_agent.store import get_a2a_agent_instance_store
|
|
203
|
+
|
|
204
|
+
agent = next((a for a in context.deployed_agents if a.agent_name == self.agent_name), None)
|
|
205
|
+
if agent is None:
|
|
206
|
+
raise RuntimeError(f"Agent '{self.agent_name}' not found in context.deployed_agents")
|
|
207
|
+
if agent.instance_id is None:
|
|
208
|
+
raise RuntimeError(f"Agent '{self.agent_name}' is missing instance_id; cannot resolve deployment")
|
|
209
|
+
|
|
210
|
+
skills_to_add = list(self.skills)
|
|
211
|
+
agent_clis = (context.metadata.get("installed_clis") or {}).get(self.agent_name) or {}
|
|
212
|
+
for cli_artifact_id in self.cli_artifact_ids:
|
|
213
|
+
entry = agent_clis.get(cli_artifact_id)
|
|
214
|
+
if entry is None:
|
|
215
|
+
raise RuntimeError(
|
|
216
|
+
f"CliArtifact '{cli_artifact_id}' not in context.installed_clis['{self.agent_name}']; "
|
|
217
|
+
f"ensure LoadArtifactTaskStep ran first with agent_name='{self.agent_name}'"
|
|
218
|
+
)
|
|
219
|
+
skills_to_add.append(_build_skill_for_installed_cli(cli_artifact_id, entry))
|
|
220
|
+
|
|
221
|
+
loaded_universes_by_id: dict[str, dict] = {
|
|
222
|
+
e["id"]: e
|
|
223
|
+
for e in (context.metadata.get("loaded_file_artifact_universes") or [])
|
|
224
|
+
if e.get("agent_name") == self.agent_name
|
|
225
|
+
}
|
|
226
|
+
for fau_id in self.file_artifact_universe_ids:
|
|
227
|
+
entry = loaded_universes_by_id.get(fau_id)
|
|
228
|
+
if entry is None:
|
|
229
|
+
raise RuntimeError(
|
|
230
|
+
f"FileArtifactUniverse '{fau_id}' was not loaded into agent '{self.agent_name}'; "
|
|
231
|
+
f"ensure a LoadArtifactTaskStep with agent_name='{self.agent_name}' for this FAU ran first"
|
|
232
|
+
)
|
|
233
|
+
skills_to_add.append(_build_skill_for_loaded_file_artifact_universe(fau_id, entry))
|
|
234
|
+
|
|
235
|
+
deployed = get_a2a_agent_instance_store().get(agent.instance_id)
|
|
236
|
+
for skill in skills_to_add:
|
|
237
|
+
result = await A2AAgent.add_skill(deployed, skill)
|
|
238
|
+
logger.info(f"Registered skill: {result}")
|
|
239
|
+
|
|
240
|
+
return context
|