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