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,197 @@
|
|
|
1
|
+
"""Deploy environment task step."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
from typing import ClassVar, Optional
|
|
8
|
+
|
|
9
|
+
from agent_env.env.gateway import GatewayMode
|
|
10
|
+
from agent_env.env.store import update_env_instance_metadata
|
|
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
|
+
from agent_env.attribution import deploy_attribution
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DeployEnvTaskStep(TaskStep):
|
|
20
|
+
type: ClassVar[str] = "deploy_env"
|
|
21
|
+
entity_refs = (EntityRef.env("env_id", version_field="env_version"),)
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
id: str,
|
|
26
|
+
version: Optional[int],
|
|
27
|
+
env_id: str,
|
|
28
|
+
env_version: Optional[int] = None,
|
|
29
|
+
ttl_seconds: int = TaskStep.DEFAULT_TTL_SECONDS,
|
|
30
|
+
disk_size_gb: float = 10,
|
|
31
|
+
gateway_mode: str = GatewayMode.PERFORMANCE.value,
|
|
32
|
+
cpu: Optional[float] = None,
|
|
33
|
+
memory_mb: Optional[int] = None,
|
|
34
|
+
sandbox_type: Optional[str] = None,
|
|
35
|
+
priority: Optional[int] = None,
|
|
36
|
+
env_state_type: Optional[str] = None,
|
|
37
|
+
env_state_instance_id: Optional[str] = None,
|
|
38
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
39
|
+
fail_task_on_error: bool = True,
|
|
40
|
+
metadata: Optional[dict] = None,
|
|
41
|
+
):
|
|
42
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
43
|
+
self.env_id = env_id
|
|
44
|
+
self.env_version = env_version
|
|
45
|
+
self.ttl_seconds = ttl_seconds
|
|
46
|
+
self.disk_size_gb = disk_size_gb
|
|
47
|
+
self.gateway_mode = gateway_mode
|
|
48
|
+
self.cpu = cpu
|
|
49
|
+
self.memory_mb = memory_mb
|
|
50
|
+
self.sandbox_type = sandbox_type
|
|
51
|
+
self.env_state_type = env_state_type
|
|
52
|
+
# Existing env state instance to attach to; the provider interprets it.
|
|
53
|
+
self.env_state_instance_id = env_state_instance_id
|
|
54
|
+
self.priority = priority
|
|
55
|
+
self.metadata = metadata or {}
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict:
|
|
58
|
+
base = super().to_dict()
|
|
59
|
+
base["env_id"] = self.env_id
|
|
60
|
+
base["env_version"] = self.env_version
|
|
61
|
+
base["ttl_seconds"] = self.ttl_seconds
|
|
62
|
+
base["disk_size_gb"] = self.disk_size_gb
|
|
63
|
+
base["gateway_mode"] = self.gateway_mode
|
|
64
|
+
base["cpu"] = self.cpu
|
|
65
|
+
base["memory_mb"] = self.memory_mb
|
|
66
|
+
base["sandbox_type"] = self.sandbox_type
|
|
67
|
+
base["env_state_type"] = self.env_state_type
|
|
68
|
+
base["env_state_instance_id"] = self.env_state_instance_id
|
|
69
|
+
base["priority"] = self.priority
|
|
70
|
+
base["metadata"] = self.metadata
|
|
71
|
+
return base
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(cls, data: dict) -> DeployEnvTaskStep:
|
|
75
|
+
return cls(
|
|
76
|
+
**cls._base_from_dict(data),
|
|
77
|
+
env_id=data["env_id"],
|
|
78
|
+
env_version=data.get("env_version"),
|
|
79
|
+
ttl_seconds=data.get("ttl_seconds", TaskStep.DEFAULT_TTL_SECONDS),
|
|
80
|
+
disk_size_gb=data.get("disk_size_gb", 10),
|
|
81
|
+
gateway_mode=data.get("gateway_mode", GatewayMode.PERFORMANCE.value),
|
|
82
|
+
cpu=data.get("cpu"),
|
|
83
|
+
memory_mb=data.get("memory_mb"),
|
|
84
|
+
sandbox_type=data.get("sandbox_type"),
|
|
85
|
+
env_state_type=data.get("env_state_type"),
|
|
86
|
+
env_state_instance_id=data.get("env_state_instance_id"),
|
|
87
|
+
priority=data.get("priority"),
|
|
88
|
+
metadata=dict(data.get("metadata") or {}),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def preflight(self) -> list[str]:
|
|
92
|
+
"""An invalid gateway_mode, an env this step can't load, or an option it would refuse at deploy, reported at save instead."""
|
|
93
|
+
from agent_env.env.env import Env
|
|
94
|
+
from agent_env.env.envs.mcp_server import MCPServerEnv
|
|
95
|
+
from agent_env.env.envs.multi_env import MultiEnv
|
|
96
|
+
from agent_env.env.envs.website import WebsiteEnv
|
|
97
|
+
from agent_env.store.base import NotFoundError
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
GatewayMode(self.gateway_mode)
|
|
101
|
+
except ValueError as e:
|
|
102
|
+
return [f"deploy_env '{self.id}': {e}"] # the words execute() would raise
|
|
103
|
+
try:
|
|
104
|
+
env = Env.get(self.env_id, self.env_version)
|
|
105
|
+
if not isinstance(env, (MCPServerEnv, WebsiteEnv, MultiEnv)):
|
|
106
|
+
return []
|
|
107
|
+
# As stored: run-time overrides aren't known at save, and deploy() consumes sandbox_type itself. Every run passes an
|
|
108
|
+
# attribution, so the check does too, whatever it will hold.
|
|
109
|
+
refusal = env.deploy_refusal(ttl_seconds=self.ttl_seconds, disk_size_gb=self.disk_size_gb, gateway_mode=self.gateway_mode, cpu=self.cpu,
|
|
110
|
+
memory_mb=self.memory_mb, priority=self.priority, env_state_type=self.env_state_type,
|
|
111
|
+
env_state_instance_id=self.env_state_instance_id, attribution={})
|
|
112
|
+
except (NotFoundError, ValueError) as e: # missing, or a type or env_provider_type this process can't load
|
|
113
|
+
return [f"deploy_env '{self.id}': env '{self.env_id}' can't be loaded: {e}"]
|
|
114
|
+
return [f"deploy_env '{self.id}': {refusal}"] if refusal else []
|
|
115
|
+
|
|
116
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
117
|
+
from agent_env.env.env import Env
|
|
118
|
+
|
|
119
|
+
existing = next((d for d in context.deployed_envs if d.env_id == self.env_id), None)
|
|
120
|
+
if existing is not None:
|
|
121
|
+
raise RuntimeError(f"Env '{self.env_id}' is already deployed")
|
|
122
|
+
|
|
123
|
+
env = Env.get(self.env_id, self.env_version)
|
|
124
|
+
user_overrides = context.metadata.get("user_overrides", {})
|
|
125
|
+
resolved_sandbox_type = user_overrides.get("env_sandbox") or self.sandbox_type
|
|
126
|
+
resolved_env_state_type = user_overrides.get("env_state_type") or self.env_state_type
|
|
127
|
+
resolved_env_state_instance_id = (
|
|
128
|
+
user_overrides.get("env_state_instance_id") or self.env_state_instance_id
|
|
129
|
+
)
|
|
130
|
+
_priority_override = user_overrides.get("priority")
|
|
131
|
+
resolved_priority = _priority_override if _priority_override is not None else self.priority
|
|
132
|
+
_ttl_override = user_overrides.get("ttl_seconds")
|
|
133
|
+
resolved_ttl = _ttl_override if _ttl_override is not None else self.ttl_seconds
|
|
134
|
+
attribution = deploy_attribution(self, context)
|
|
135
|
+
logger.info(
|
|
136
|
+
f"Deploying env '{self.env_id}' (ttl={resolved_ttl}s, "
|
|
137
|
+
f"disk_size_gb={self.disk_size_gb}, gateway_mode={self.gateway_mode}, "
|
|
138
|
+
f"cpu={self.cpu}, memory_mb={self.memory_mb}, "
|
|
139
|
+
f"sandbox_type={resolved_sandbox_type}, env_state_type={resolved_env_state_type}, "
|
|
140
|
+
f"env_state_instance_id={resolved_env_state_instance_id}, "
|
|
141
|
+
f"attribution={attribution}, priority={resolved_priority})"
|
|
142
|
+
)
|
|
143
|
+
deploy_kwargs = {
|
|
144
|
+
"ttl_seconds": resolved_ttl,
|
|
145
|
+
"disk_size_gb": self.disk_size_gb,
|
|
146
|
+
"gateway_mode": GatewayMode(self.gateway_mode),
|
|
147
|
+
"cpu": self.cpu,
|
|
148
|
+
"memory_mb": self.memory_mb,
|
|
149
|
+
"priority": resolved_priority,
|
|
150
|
+
"attribution": attribution,
|
|
151
|
+
}
|
|
152
|
+
if resolved_sandbox_type is not None:
|
|
153
|
+
deploy_kwargs["sandbox_type"] = resolved_sandbox_type
|
|
154
|
+
if resolved_env_state_type is not None:
|
|
155
|
+
deploy_kwargs["env_state_type"] = resolved_env_state_type
|
|
156
|
+
if resolved_env_state_instance_id is not None:
|
|
157
|
+
deploy_kwargs["env_state_instance_id"] = resolved_env_state_instance_id
|
|
158
|
+
# Forward the per-run LiteLLM key so envs that spin up their own LLM-calling
|
|
159
|
+
# sidecars attribute spend to the same key as the agent. Guard on the
|
|
160
|
+
# signature — most envs' deploy() takes a fixed param list and would
|
|
161
|
+
# TypeError on an unexpected kwarg.
|
|
162
|
+
params = inspect.signature(env.deploy).parameters
|
|
163
|
+
accepts_kwargs = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values())
|
|
164
|
+
model_api_key = user_overrides.get("litellm_api_key")
|
|
165
|
+
if model_api_key and ("litellm_api_key" in params or accepts_kwargs):
|
|
166
|
+
deploy_kwargs["litellm_api_key"] = model_api_key
|
|
167
|
+
deployed_env = await env.deploy(**deploy_kwargs)
|
|
168
|
+
# create_instance runs inside env.deploy(), so an annotation set after it
|
|
169
|
+
# returns has to be persisted explicitly to reach the instance record.
|
|
170
|
+
annotations = {"deploy_step_id": self.id}
|
|
171
|
+
deployed_env.metadata = {**(deployed_env.metadata or {}), **annotations}
|
|
172
|
+
if deployed_env.instance_id:
|
|
173
|
+
update_env_instance_metadata(deployed_env.instance_id, annotations)
|
|
174
|
+
context.deployed_envs.append(deployed_env)
|
|
175
|
+
|
|
176
|
+
# Some envs (e.g. harbor coding tasks) build + run the agent as part of
|
|
177
|
+
# deploy() and have no separate DeployAgent step. They advertise the
|
|
178
|
+
# running agent via DeployedEnv.metadata["deployed_agent"]; register it
|
|
179
|
+
# so PromptAgent can resolve it by name from context.deployed_agents.
|
|
180
|
+
agent_info = (deployed_env.metadata or {}).get("deployed_agent")
|
|
181
|
+
if agent_info:
|
|
182
|
+
from agent_env.task_step.context import DeployedAgent
|
|
183
|
+
|
|
184
|
+
if any(a.agent_name == agent_info["agent_name"] for a in context.deployed_agents):
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"Agent '{agent_info['agent_name']}' already in context.deployed_agents"
|
|
187
|
+
)
|
|
188
|
+
context.deployed_agents.append(DeployedAgent(
|
|
189
|
+
agent_name=agent_info["agent_name"],
|
|
190
|
+
api_url=agent_info["api_url"],
|
|
191
|
+
sandbox_id=agent_info.get("sandbox_id"),
|
|
192
|
+
sandbox_type=agent_info.get("sandbox_type"),
|
|
193
|
+
a2a_url=agent_info.get("a2a_url"),
|
|
194
|
+
a2a_card=agent_info.get("a2a_card"),
|
|
195
|
+
instance_id=agent_info.get("instance_id") or deployed_env.instance_id,
|
|
196
|
+
))
|
|
197
|
+
return context
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Register an external human A2A endpoint as a peerable DeployedAgent, so a
|
|
2
|
+
single-turn solver can consult a human via the peer_send_message tool. A peer
|
|
3
|
+
needs only a name + URL, so the card is best-effort.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import ClassVar, Optional
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from agent_env.task_step.context import DeployedAgent, TaskStepContext
|
|
14
|
+
from agent_env.task_step.task_step import TaskStep, TaskStepDependency
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
_CARD_FETCH_TIMEOUT_SECONDS = 5
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DeployHumanAgentTaskStep(TaskStep):
|
|
22
|
+
type: ClassVar[str] = "deploy_human_agent"
|
|
23
|
+
entity_refs = ()
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
id: str,
|
|
28
|
+
version: Optional[int],
|
|
29
|
+
agent_name: str = "human_agent",
|
|
30
|
+
a2a_url: Optional[str] = None,
|
|
31
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
32
|
+
fail_task_on_error: bool = True,
|
|
33
|
+
):
|
|
34
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
35
|
+
self.agent_name = agent_name
|
|
36
|
+
self.a2a_url = a2a_url
|
|
37
|
+
|
|
38
|
+
def to_dict(self) -> dict:
|
|
39
|
+
base = super().to_dict()
|
|
40
|
+
base["agent_name"] = self.agent_name
|
|
41
|
+
base["a2a_url"] = self.a2a_url
|
|
42
|
+
return base
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_dict(cls, data: dict) -> DeployHumanAgentTaskStep:
|
|
46
|
+
return cls(
|
|
47
|
+
**cls._base_from_dict(data),
|
|
48
|
+
agent_name=data.get("agent_name", "human_agent"),
|
|
49
|
+
a2a_url=data.get("a2a_url"),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
53
|
+
from agent_env.config import get_config
|
|
54
|
+
|
|
55
|
+
if context.metadata.get("human_agents"):
|
|
56
|
+
raise RuntimeError(
|
|
57
|
+
"DeployHumanAgentTaskStep: a human agent is already registered; "
|
|
58
|
+
"only one human agent is allowed per task"
|
|
59
|
+
)
|
|
60
|
+
if any(a.agent_name == self.agent_name for a in context.deployed_agents):
|
|
61
|
+
raise RuntimeError(f"Agent with name '{self.agent_name}' is already deployed")
|
|
62
|
+
|
|
63
|
+
if self.a2a_url:
|
|
64
|
+
url = self.a2a_url
|
|
65
|
+
else:
|
|
66
|
+
base = get_config().get_default_human_a2a_url()
|
|
67
|
+
# Run-scope the URL so the human endpoint can attribute the ask to this
|
|
68
|
+
# task instance from the request path.
|
|
69
|
+
url = f"{base.rstrip('/')}/instance/{context.instance_id}" if context.instance_id else base
|
|
70
|
+
card = await self._resolve_card(url)
|
|
71
|
+
|
|
72
|
+
context.deployed_agents.append(
|
|
73
|
+
DeployedAgent(agent_name=self.agent_name, api_url=url, a2a_url=url, a2a_card=card)
|
|
74
|
+
)
|
|
75
|
+
context.metadata.setdefault("human_agents", []).append(
|
|
76
|
+
{"step_id": self.id, "agent_name": self.agent_name, "a2a_url": url}
|
|
77
|
+
)
|
|
78
|
+
logger.info(f"Registered human peer '{self.agent_name}' at {url} (card name={card.get('name')})")
|
|
79
|
+
return context
|
|
80
|
+
|
|
81
|
+
async def _resolve_card(self, url: str) -> dict:
|
|
82
|
+
"""Best-effort card fetch — a human endpoint may not serve one, and a peer needs no card, so fall back to a synthesized card."""
|
|
83
|
+
try:
|
|
84
|
+
async with httpx.AsyncClient() as client:
|
|
85
|
+
resp = await client.get(f"{url}/.well-known/agent.json", timeout=_CARD_FETCH_TIMEOUT_SECONDS)
|
|
86
|
+
if resp.status_code == 200:
|
|
87
|
+
return resp.json()
|
|
88
|
+
logger.warning(f"Human agent card at {url} returned {resp.status_code}; using a synthesized card")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.warning(f"Could not fetch human agent card from {url} ({type(e).__name__}: {e}); using a synthesized card")
|
|
91
|
+
return self._synthesized_card()
|
|
92
|
+
|
|
93
|
+
def _synthesized_card(self) -> dict:
|
|
94
|
+
return {
|
|
95
|
+
"name": self.agent_name,
|
|
96
|
+
"description": "A human operator available to answer questions",
|
|
97
|
+
"capabilities": {},
|
|
98
|
+
"default_input_modes": ["text"],
|
|
99
|
+
"default_output_modes": ["text"],
|
|
100
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Deploy bare sandbox task step."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import ClassVar, Optional
|
|
7
|
+
|
|
8
|
+
from agent_env.providers.sandbox_providers.sandbox import NetworkPolicy
|
|
9
|
+
from agent_env.task_step.context import DeployedSandbox, TaskStepContext
|
|
10
|
+
from agent_env.task_step.task_step import TaskStep, TaskStepDependency
|
|
11
|
+
from agent_env.attribution import deploy_attribution
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_SUPPORTED_MODES = {"vm", "container"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DeploySandboxTaskStep(TaskStep):
|
|
19
|
+
type: ClassVar[str] = "deploy_sandbox"
|
|
20
|
+
entity_refs = ()
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
id: str,
|
|
25
|
+
version: Optional[int],
|
|
26
|
+
sandbox_name: str,
|
|
27
|
+
sandbox_mode: str,
|
|
28
|
+
image: Optional[str] = None,
|
|
29
|
+
cpu: float = 2.0,
|
|
30
|
+
memory_mb: int = 8192,
|
|
31
|
+
disk_size_gb: float = 10,
|
|
32
|
+
ttl_seconds: int = TaskStep.DEFAULT_TTL_SECONDS,
|
|
33
|
+
exposed_ports: Optional[list[int]] = None,
|
|
34
|
+
boot_mode: Optional[str] = None,
|
|
35
|
+
port: Optional[int] = None,
|
|
36
|
+
env_vars: Optional[dict[str, str]] = None,
|
|
37
|
+
sandbox_type: Optional[str] = None,
|
|
38
|
+
priority: Optional[int] = None,
|
|
39
|
+
network_policy: Optional[dict] = None,
|
|
40
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
41
|
+
fail_task_on_error: bool = True,
|
|
42
|
+
metadata: Optional[dict] = None,
|
|
43
|
+
):
|
|
44
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
45
|
+
if sandbox_mode not in _SUPPORTED_MODES:
|
|
46
|
+
raise ValueError(f"sandbox_mode must be one of {sorted(_SUPPORTED_MODES)}, got {sandbox_mode!r}")
|
|
47
|
+
self.sandbox_name = sandbox_name
|
|
48
|
+
self.sandbox_mode = sandbox_mode
|
|
49
|
+
self.image = image
|
|
50
|
+
self.cpu = cpu
|
|
51
|
+
self.memory_mb = memory_mb
|
|
52
|
+
self.disk_size_gb = disk_size_gb
|
|
53
|
+
self.ttl_seconds = ttl_seconds
|
|
54
|
+
self.exposed_ports = exposed_ports
|
|
55
|
+
self.boot_mode = boot_mode
|
|
56
|
+
self.port = port
|
|
57
|
+
self.env_vars = env_vars
|
|
58
|
+
self.sandbox_type = sandbox_type
|
|
59
|
+
self.priority = priority
|
|
60
|
+
self.metadata = metadata or {}
|
|
61
|
+
self.network_policy = NetworkPolicy.from_dict(network_policy).to_dict() if network_policy else None
|
|
62
|
+
|
|
63
|
+
def to_dict(self) -> dict:
|
|
64
|
+
base = super().to_dict()
|
|
65
|
+
base["sandbox_name"] = self.sandbox_name
|
|
66
|
+
base["sandbox_mode"] = self.sandbox_mode
|
|
67
|
+
base["image"] = self.image
|
|
68
|
+
base["cpu"] = self.cpu
|
|
69
|
+
base["memory_mb"] = self.memory_mb
|
|
70
|
+
base["disk_size_gb"] = self.disk_size_gb
|
|
71
|
+
base["ttl_seconds"] = self.ttl_seconds
|
|
72
|
+
base["exposed_ports"] = self.exposed_ports
|
|
73
|
+
base["boot_mode"] = self.boot_mode
|
|
74
|
+
base["port"] = self.port
|
|
75
|
+
base["env_vars"] = self.env_vars
|
|
76
|
+
base["sandbox_type"] = self.sandbox_type
|
|
77
|
+
base["priority"] = self.priority
|
|
78
|
+
base["metadata"] = self.metadata
|
|
79
|
+
base["network_policy"] = self.network_policy
|
|
80
|
+
return base
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_dict(cls, data: dict) -> DeploySandboxTaskStep:
|
|
84
|
+
return cls(
|
|
85
|
+
**cls._base_from_dict(data),
|
|
86
|
+
sandbox_name=data["sandbox_name"],
|
|
87
|
+
sandbox_mode=data["sandbox_mode"],
|
|
88
|
+
image=data.get("image"),
|
|
89
|
+
cpu=data.get("cpu", 2.0),
|
|
90
|
+
memory_mb=data.get("memory_mb", 8192),
|
|
91
|
+
disk_size_gb=data.get("disk_size_gb", 10),
|
|
92
|
+
ttl_seconds=data.get("ttl_seconds", TaskStep.DEFAULT_TTL_SECONDS),
|
|
93
|
+
exposed_ports=data.get("exposed_ports"),
|
|
94
|
+
boot_mode=data.get("boot_mode"),
|
|
95
|
+
port=data.get("port"),
|
|
96
|
+
env_vars=data.get("env_vars"),
|
|
97
|
+
sandbox_type=data.get("sandbox_type"),
|
|
98
|
+
priority=data.get("priority"),
|
|
99
|
+
metadata=dict(data.get("metadata") or {}),
|
|
100
|
+
network_policy=data.get("network_policy"),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
104
|
+
from agent_env.providers.sandbox_providers.sandbox_provider import build_sandbox_provider, get_sandbox_provider
|
|
105
|
+
|
|
106
|
+
if any(s.sandbox_name == self.sandbox_name for s in context.deployed_sandboxes):
|
|
107
|
+
raise RuntimeError(f"Sandbox '{self.sandbox_name}' is already deployed")
|
|
108
|
+
|
|
109
|
+
user_overrides = context.metadata.get("user_overrides") or {}
|
|
110
|
+
# Moves only deploy_sandbox steps: deploy_env and deploy_agent read env_sandbox and agent_sandbox.
|
|
111
|
+
resolved_sandbox_type = user_overrides.get("sandbox") or self.sandbox_type
|
|
112
|
+
provider = (
|
|
113
|
+
build_sandbox_provider(resolved_sandbox_type)
|
|
114
|
+
if resolved_sandbox_type
|
|
115
|
+
else get_sandbox_provider()
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
_ttl_override = user_overrides.get("ttl_seconds")
|
|
119
|
+
resolved_ttl = _ttl_override if _ttl_override is not None else self.ttl_seconds
|
|
120
|
+
attribution = deploy_attribution(self, context)
|
|
121
|
+
logger.info(
|
|
122
|
+
f"Deploying sandbox '{self.sandbox_name}' (mode={self.sandbox_mode}, "
|
|
123
|
+
f"image={self.image}, cpu={self.cpu}, memory_mb={self.memory_mb}, "
|
|
124
|
+
f"disk_size_gb={self.disk_size_gb}, ttl_seconds={resolved_ttl}, "
|
|
125
|
+
f"sandbox_type={resolved_sandbox_type}, attribution={attribution})"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
_override = user_overrides.get("network_policy")
|
|
129
|
+
_resolved = _override if _override is not None else self.network_policy
|
|
130
|
+
policy = NetworkPolicy.from_dict(_resolved) if _resolved is not None else None
|
|
131
|
+
|
|
132
|
+
if self.sandbox_mode == "vm":
|
|
133
|
+
sandbox = await provider.create_vm(
|
|
134
|
+
image=self.image,
|
|
135
|
+
boot_mode=self.boot_mode,
|
|
136
|
+
cpu=self.cpu,
|
|
137
|
+
memory=self.memory_mb,
|
|
138
|
+
disk_size_gb=self.disk_size_gb,
|
|
139
|
+
timeout=resolved_ttl,
|
|
140
|
+
exposed_ports=self.exposed_ports or [],
|
|
141
|
+
attribution=attribution,
|
|
142
|
+
priority=self.priority,
|
|
143
|
+
network_policy=policy,
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
if self.image is None or self.port is None:
|
|
147
|
+
raise ValueError("container-mode sandbox requires `image` and `port`")
|
|
148
|
+
sandbox = await provider.create_sandbox(
|
|
149
|
+
image_name=self.image,
|
|
150
|
+
port=self.port,
|
|
151
|
+
env=self.env_vars or {},
|
|
152
|
+
cpu=self.cpu,
|
|
153
|
+
memory=self.memory_mb,
|
|
154
|
+
disk_size_gb=self.disk_size_gb,
|
|
155
|
+
timeout=resolved_ttl,
|
|
156
|
+
attribution=attribution,
|
|
157
|
+
priority=self.priority,
|
|
158
|
+
network_policy=policy,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
context.deployed_sandboxes.append(DeployedSandbox(
|
|
162
|
+
sandbox_name=self.sandbox_name,
|
|
163
|
+
sandbox_id=sandbox.sandbox_id,
|
|
164
|
+
sandbox_mode=sandbox.mode,
|
|
165
|
+
sandbox_type=sandbox.type,
|
|
166
|
+
tunnel_urls={str(p): u for p, u in sandbox.tunnel_urls.items()} if sandbox.tunnel_urls else None,
|
|
167
|
+
vnc_url=sandbox.vnc_url,
|
|
168
|
+
network_policy=sandbox.network_policy.to_dict() if sandbox.network_policy else None,
|
|
169
|
+
))
|
|
170
|
+
return context
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
from .verify_env_card import ENVIRONMENT_CARD_KEY, VALIDATED_ENVIRONMENT_CARD_KEY, VerifyEnvironmentCardStep
|
|
2
|
+
from .verify_env_core_protocol import VALIDATED_ENVIRONMENT_PROTOCOL_KEY, VerifyCoreEnvironmentProtocolStep
|
|
3
|
+
|
|
4
|
+
__all__ = ["VerifyEnvironmentCardStep", "VerifyCoreEnvironmentProtocolStep", "ENVIRONMENT_CARD_KEY", "VALIDATED_ENVIRONMENT_CARD_KEY", "VALIDATED_ENVIRONMENT_PROTOCOL_KEY"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Verify the EnvironmentCard a deployed environment serves at its card's address, and persist it to env metadata."""
|
|
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
|
+
REQUIRED_CARD_FIELDS = ["name", "protocolVersion", "url", "preferredTransport", "capabilities"]
|
|
15
|
+
|
|
16
|
+
# Env-doc metadata keys the validator writes (read back by the CLIs / hub consumers).
|
|
17
|
+
ENVIRONMENT_CARD_KEY = "environment_card"
|
|
18
|
+
VALIDATED_ENVIRONMENT_CARD_KEY = "validated_environment_card"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VerifyEnvironmentCardStep(TaskStep):
|
|
22
|
+
type: ClassVar[str] = "verify_env_card"
|
|
23
|
+
entity_refs = (EntityRef.env("env_id"),)
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
id: str,
|
|
28
|
+
version: Optional[int],
|
|
29
|
+
env_id: str,
|
|
30
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
31
|
+
fail_task_on_error: bool = True,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
34
|
+
self.env_id = env_id
|
|
35
|
+
|
|
36
|
+
def to_dict(self) -> dict:
|
|
37
|
+
base = super().to_dict()
|
|
38
|
+
base["env_id"] = self.env_id
|
|
39
|
+
return base
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_dict(cls, data: dict) -> VerifyEnvironmentCardStep:
|
|
43
|
+
return cls(**cls._base_from_dict(data), env_id=data["env_id"])
|
|
44
|
+
|
|
45
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
46
|
+
from agentenv_protocol import client as protocol_v1
|
|
47
|
+
from agent_env.env.env import Env
|
|
48
|
+
|
|
49
|
+
deployed = next((d for d in context.deployed_envs if d.env_id == self.env_id), None)
|
|
50
|
+
if deployed is None:
|
|
51
|
+
raise RuntimeError(f"Env '{self.env_id}' not found in context.deployed_envs")
|
|
52
|
+
|
|
53
|
+
card = None
|
|
54
|
+
error = None
|
|
55
|
+
try:
|
|
56
|
+
card = await protocol_v1.get_card(deployed.environment_url)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
error = f"{type(e).__name__}: {e}"
|
|
59
|
+
logger.warning(f"env card for '{self.env_id}' inaccessible at {deployed.environment_url}: {error}")
|
|
60
|
+
|
|
61
|
+
env = Env.get(self.env_id, deployed.env_version)
|
|
62
|
+
|
|
63
|
+
if not card:
|
|
64
|
+
validated = {"accessible": False, "error": error or "empty card"}
|
|
65
|
+
env.update_metadata({**env.metadata, VALIDATED_ENVIRONMENT_CARD_KEY: validated})
|
|
66
|
+
context.metadata.setdefault("verifications", {})["environment_card"] = {VALIDATED_ENVIRONMENT_CARD_KEY: validated}
|
|
67
|
+
return context
|
|
68
|
+
|
|
69
|
+
required_fields = {}
|
|
70
|
+
missing_required = []
|
|
71
|
+
for field in REQUIRED_CARD_FIELDS:
|
|
72
|
+
present = field in card and bool(card[field])
|
|
73
|
+
required_fields[field] = {"present": present}
|
|
74
|
+
if not present:
|
|
75
|
+
missing_required.append(field)
|
|
76
|
+
if missing_required:
|
|
77
|
+
logger.warning(f"env card '{self.env_id}' missing required fields: {missing_required}")
|
|
78
|
+
|
|
79
|
+
children = card.get("children_environments") or []
|
|
80
|
+
capabilities = card.get("capabilities") or {}
|
|
81
|
+
extensions = [uri for e in capabilities.get("extensions") or [] if (uri := e.get("uri"))]
|
|
82
|
+
tools = [name for t in capabilities.get("tools") or [] if (name := t.get("name"))]
|
|
83
|
+
logger.info(f"env card '{self.env_id}': name={card.get('name')} children={len(children)} extensions={extensions} tools={tools}")
|
|
84
|
+
|
|
85
|
+
registered_name = getattr(env, "environment_name", None)
|
|
86
|
+
served_names = [c.get("name") for c in children if c.get("name")]
|
|
87
|
+
name_check = None
|
|
88
|
+
if registered_name and served_names:
|
|
89
|
+
matches = registered_name in served_names
|
|
90
|
+
name_check = {"registered_name": registered_name, "served_card_names": served_names, "matches": matches}
|
|
91
|
+
if not matches:
|
|
92
|
+
logger.warning(
|
|
93
|
+
f"env card name drift for '{self.env_id}': registered environment_name={registered_name!r} is not "
|
|
94
|
+
f"among the served card names {served_names!r} — the image's agentenv-protocol likely predates "
|
|
95
|
+
f"ENVIRONMENT_NAME taking precedence over @environment_card(name=...); rebuild it on a current SDK."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
validated = {
|
|
99
|
+
"accessible": True,
|
|
100
|
+
"required_fields": required_fields,
|
|
101
|
+
"children_count": len(children),
|
|
102
|
+
"extensions": extensions,
|
|
103
|
+
"tools": tools,
|
|
104
|
+
"name_check": name_check,
|
|
105
|
+
}
|
|
106
|
+
env.update_metadata({**env.metadata, ENVIRONMENT_CARD_KEY: card, VALIDATED_ENVIRONMENT_CARD_KEY: validated})
|
|
107
|
+
|
|
108
|
+
context.metadata.setdefault("verifications", {})["environment_card"] = {ENVIRONMENT_CARD_KEY: card, VALIDATED_ENVIRONMENT_CARD_KEY: validated}
|
|
109
|
+
return context
|