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,237 @@
|
|
|
1
|
+
"""Environment providers: the contract a provider implements, the built-ins' shared base, and finding a provider by type."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, ClassVar
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from mcp import ClientSession
|
|
13
|
+
from mcp.client.streamable_http import streamable_http_client
|
|
14
|
+
from agent_env.env.env import DeployedEnv
|
|
15
|
+
from agent_env.env.gateway.constants import WELL_KNOWN_PATH
|
|
16
|
+
from agent_env.plugins import _registration
|
|
17
|
+
from agent_env.providers.sandbox_providers.sandbox import Sandbox
|
|
18
|
+
from agent_env.providers.sandbox_providers.sandbox_provider import SANDBOX_MODE_CONTAINER
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from agent_env.config.runtime import Config
|
|
22
|
+
from agent_env.env.env import Env
|
|
23
|
+
from agent_env.providers.sandbox_providers.sandbox_provider import SandboxProvider
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EnvironmentProvider(ABC):
|
|
29
|
+
"""Deploys an env and hands back its record; tears down what it created."""
|
|
30
|
+
|
|
31
|
+
type: ClassVar[str] # the name an env declares it by, and that its records carry as env_provider_type
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
async def deploy(self, env: Env, sandbox_provider: SandboxProvider) -> DeployedEnv:
|
|
35
|
+
"""Deploy the env and return its record, unregistered: the env registers it. Take ``**options``, which receives every option
|
|
36
|
+
the env's deploy was given; a deploy that names its options instead is refused any other one set away from its default."""
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def close(self) -> None:
|
|
40
|
+
"""Tear down everything deploy() created."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def record_class_for(env_provider_type: str | None) -> type[DeployedEnv] | None:
|
|
44
|
+
"""The record class a built-in provider type writes; None for any other type, whose records load by shape."""
|
|
45
|
+
provider = _builtin_env_providers().get(env_provider_type)
|
|
46
|
+
return provider.record_class if provider is not None else None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_env_provider(env_provider_type: str) -> EnvironmentProvider:
|
|
50
|
+
"""A fresh provider of the type an env declares, built in or from a plugin; raises for a type this process can't find."""
|
|
51
|
+
return _env_provider_class(env_provider_type)()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _env_provider_class(env_provider_type: str) -> type[EnvironmentProvider]:
|
|
55
|
+
"""The registered provider class of that type; raises, naming the registered types, for one this process can't find."""
|
|
56
|
+
from agent_env.config import runtime
|
|
57
|
+
|
|
58
|
+
providers = runtime.get_config().env_provider_registry()
|
|
59
|
+
if env_provider_type not in providers:
|
|
60
|
+
raise ValueError(f"Unknown env_provider_type: {env_provider_type!r} (expected one of {sorted(providers)})"
|
|
61
|
+
f"{_registration.failure_note(_registration.ENV_PROVIDERS, env_provider_type)}")
|
|
62
|
+
return providers[env_provider_type]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _SandboxEnvironmentProvider(EnvironmentProvider):
|
|
66
|
+
"""The built-in providers' base: tracks the containers it creates in our sandboxes, closes them, reattaches to the records it wrote,
|
|
67
|
+
and retries across a chained spec."""
|
|
68
|
+
|
|
69
|
+
record_class: ClassVar[type[DeployedEnv]] = DeployedEnv # the class its records load back as
|
|
70
|
+
|
|
71
|
+
def __init__(self):
|
|
72
|
+
self._container_sandboxes: list[Sandbox] = []
|
|
73
|
+
self._environment_sandboxes: dict[str, Sandbox] = {}
|
|
74
|
+
|
|
75
|
+
def environment_sandbox(self, environment_name: str) -> Sandbox | None:
|
|
76
|
+
return self._environment_sandboxes.get(environment_name)
|
|
77
|
+
|
|
78
|
+
async def close(self) -> None:
|
|
79
|
+
for sb in self._container_sandboxes:
|
|
80
|
+
try:
|
|
81
|
+
await sb.terminate()
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.warning(f"Failed to terminate {type(sb).__name__} {sb.sandbox_id}: {e}")
|
|
84
|
+
self._container_sandboxes.clear()
|
|
85
|
+
self._environment_sandboxes.clear()
|
|
86
|
+
|
|
87
|
+
async def _reattach(self, env: Env, deployed: DeployedEnv) -> Sandbox:
|
|
88
|
+
"""A record this provider wrote, reattached: its primary sandbox, or in container mode the server's container, with every recorded one for close()."""
|
|
89
|
+
from agent_env.env.envs.mcp_server import MCPServerEnv
|
|
90
|
+
from agent_env.providers import build_sandbox_provider, get_env_sandbox_provider
|
|
91
|
+
provider = build_sandbox_provider(deployed.sandbox_type) if deployed.sandbox_type else get_env_sandbox_provider()
|
|
92
|
+
sandbox = await provider.get_sandbox(deployed.sandbox_id)
|
|
93
|
+
if env.environment_name in deployed.sandbox_ids.get(MCPServerEnv.type, {}):
|
|
94
|
+
# Container mode.
|
|
95
|
+
sandbox = await self._reattach_containers(provider, sandbox, deployed.sandbox_ids, env.environment_name)
|
|
96
|
+
return sandbox
|
|
97
|
+
|
|
98
|
+
async def _reattach_containers(self, provider: SandboxProvider, gateway: Sandbox, sandbox_ids: dict, environment_name: str) -> Sandbox:
|
|
99
|
+
"""Reattach the recorded containers for close(), skipping a dead servicedb or sidecar; returns the server's container, raising if it's dead."""
|
|
100
|
+
from agent_env.env.envs.mcp_server import MCPServerEnv
|
|
101
|
+
from agent_env.env.envs.service_db import ServiceDBEnv
|
|
102
|
+
server_id = sandbox_ids[MCPServerEnv.type][environment_name]
|
|
103
|
+
db_ids = list(sandbox_ids.get(ServiceDBEnv.type, {}).values())
|
|
104
|
+
server, *dbs = await asyncio.gather(*(provider.get_sandbox(i) for i in [server_id, *db_ids]), return_exceptions=True)
|
|
105
|
+
if isinstance(server, BaseException):
|
|
106
|
+
raise RuntimeError(f"[{environment_name}] can't reattach the server's container {server_id}: {type(server).__name__}: {server}") from server
|
|
107
|
+
server.mode = SANDBOX_MODE_CONTAINER # recorded under mcp_server, so create_container made it
|
|
108
|
+
for db_id, db in zip(db_ids, dbs):
|
|
109
|
+
if isinstance(db, BaseException):
|
|
110
|
+
logger.warning(f"[{environment_name}] skipping recorded container {db_id}: {type(db).__name__}: {db}")
|
|
111
|
+
self._container_sandboxes = [server, *(db for db in dbs if not isinstance(db, BaseException)), gateway]
|
|
112
|
+
return server
|
|
113
|
+
|
|
114
|
+
async def _wait_for_tunnel(self, url: str, timeout: int = 60) -> dict | None:
|
|
115
|
+
"""Wait until the deployed env serves its card to this process; returns the card.
|
|
116
|
+
|
|
117
|
+
Probes the well-known document rather than ``/mcp``, whose healthy endpoint holds the
|
|
118
|
+
connection open. Only a 200 carrying a card counts, and that card is what the deploy
|
|
119
|
+
records. Returns None if none arrives within ``timeout`` attempts.
|
|
120
|
+
"""
|
|
121
|
+
probe_url = f"{url}{WELL_KNOWN_PATH}"
|
|
122
|
+
logger.info(f"Waiting for tunnel to be ready (timeout={timeout}s) via {probe_url}...")
|
|
123
|
+
last = "no response"
|
|
124
|
+
async with httpx.AsyncClient() as client:
|
|
125
|
+
for i in range(timeout):
|
|
126
|
+
try:
|
|
127
|
+
response = await client.get(probe_url, timeout=5)
|
|
128
|
+
card = _card_from(response)
|
|
129
|
+
if card is not None:
|
|
130
|
+
logger.info(f" Tunnel ready (HTTP {response.status_code}) after {i+1}s")
|
|
131
|
+
return card
|
|
132
|
+
last = f"HTTP {response.status_code} {response.headers.get('content-type', '')}".rstrip()
|
|
133
|
+
except Exception as e:
|
|
134
|
+
last = f"error: {type(e).__name__}"
|
|
135
|
+
if i % 10 == 0:
|
|
136
|
+
logger.info(f" Still waiting... ({last})")
|
|
137
|
+
await asyncio.sleep(1)
|
|
138
|
+
logger.info(f" Tunnel not ready after {timeout}s (last: {last})")
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def _sandbox_providers(self, sandbox_provider: SandboxProvider, env_id: str | None) -> list[SandboxProvider]:
|
|
142
|
+
"""The sandbox providers to try, in order: a chained one's members, or itself."""
|
|
143
|
+
from agent_env.providers.sandbox_providers.chained_sandbox_provider import ChainedSandboxProvider
|
|
144
|
+
|
|
145
|
+
if isinstance(sandbox_provider, ChainedSandboxProvider):
|
|
146
|
+
return list(sandbox_provider._providers)
|
|
147
|
+
return [sandbox_provider]
|
|
148
|
+
|
|
149
|
+
async def _run(self, sandbox_provider: SandboxProvider, env_id: str | None, attempt: Callable[[SandboxProvider], Awaitable[Any]]) -> Any:
|
|
150
|
+
"""Run ``attempt`` on the sandbox provider, or on each chained member until one succeeds; a failed deploy is closed."""
|
|
151
|
+
try:
|
|
152
|
+
sandbox_providers = self._sandbox_providers(sandbox_provider, env_id)
|
|
153
|
+
if len(sandbox_providers) == 1:
|
|
154
|
+
return await self._attempt(sandbox_providers[0], env_id, attempt)
|
|
155
|
+
errors: list[tuple[str, Exception]] = []
|
|
156
|
+
for p in sandbox_providers:
|
|
157
|
+
try:
|
|
158
|
+
return await self._attempt(p, env_id, attempt)
|
|
159
|
+
except Exception as e:
|
|
160
|
+
errors.append((type(p).__name__, e))
|
|
161
|
+
await self.close()
|
|
162
|
+
detail = "; ".join(f"{n}: {e!r}" for n, e in errors)
|
|
163
|
+
raise RuntimeError(f"All {len(sandbox_providers)} chained providers failed to deploy: {detail}")
|
|
164
|
+
except Exception:
|
|
165
|
+
await self.close()
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
async def _attempt(self, sandbox_provider: SandboxProvider, env_id: str | None, attempt: Callable[[SandboxProvider], Awaitable[Any]]) -> Any:
|
|
169
|
+
"""One deploy on one sandbox provider, logged as an ``env_deploy_provider_attempt``."""
|
|
170
|
+
start = time.monotonic()
|
|
171
|
+
provider_name = type(sandbox_provider).__name__
|
|
172
|
+
try:
|
|
173
|
+
result = await attempt(sandbox_provider)
|
|
174
|
+
logger.info(f"env_deploy_provider_attempt env_id={env_id or 'unknown'} provider={provider_name} status=success duration_s={time.monotonic() - start:.1f}")
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.warning(f"env_deploy_provider_attempt env_id={env_id or 'unknown'} provider={provider_name} status=failure duration_s={time.monotonic() - start:.1f} error_type={type(e).__name__} error={e!r}")
|
|
177
|
+
raise
|
|
178
|
+
return result
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _size_kwargs(cpu: float | None, memory_mb: int | None) -> dict:
|
|
182
|
+
"""Forward only what the caller set, so the provider's own default stands otherwise.
|
|
183
|
+
|
|
184
|
+
Passing None through would not reach that default: the providers hand cpu/memory
|
|
185
|
+
straight to the backend, so an explicit None means "no reservation at all" rather than
|
|
186
|
+
"your floor".
|
|
187
|
+
"""
|
|
188
|
+
kwargs: dict = {}
|
|
189
|
+
if cpu is not None:
|
|
190
|
+
kwargs["cpu"] = cpu
|
|
191
|
+
if memory_mb is not None:
|
|
192
|
+
kwargs["memory"] = memory_mb
|
|
193
|
+
return kwargs
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _card_from(response: httpx.Response) -> dict | None:
|
|
197
|
+
"""The env card in a readiness response: a 200 whose JSON body is an object with a name."""
|
|
198
|
+
if response.status_code != 200:
|
|
199
|
+
return None
|
|
200
|
+
try:
|
|
201
|
+
card = response.json()
|
|
202
|
+
except ValueError:
|
|
203
|
+
return None
|
|
204
|
+
return card if isinstance(card, dict) and isinstance(card.get("name"), str) and card["name"] else None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def _tool_names(mcp_url: str) -> list[str]:
|
|
208
|
+
"""The server's tool names as it lists them, duplicates included."""
|
|
209
|
+
async with streamable_http_client(mcp_url) as (read_stream, write_stream, _):
|
|
210
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
211
|
+
await session.initialize()
|
|
212
|
+
return [tool.name for tool in (await session.list_tools()).tools]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _builtin_env_providers() -> dict[str, type[_SandboxEnvironmentProvider]]:
|
|
216
|
+
"""The built-in providers, by type."""
|
|
217
|
+
from agent_env.providers.env_providers.env_gateway_provider import EnvironmentGatewayProvider
|
|
218
|
+
from agent_env.providers.env_providers.env_server_provider import EnvironmentServerProvider
|
|
219
|
+
|
|
220
|
+
return {p.type: p for p in (EnvironmentGatewayProvider, EnvironmentServerProvider)}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _build_registry(source: Config | None = None) -> dict[str, type[EnvironmentProvider]]:
|
|
224
|
+
"""The built-in providers, then ``agent_env.env_providers`` plugins."""
|
|
225
|
+
registry: dict[str, type[EnvironmentProvider]] = dict(_builtin_env_providers())
|
|
226
|
+
_registration.merge(registry, _registration.ENV_PROVIDERS, _validate_plugin, source=source)
|
|
227
|
+
return registry
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _validate_plugin(name: str, loaded: Any) -> type[EnvironmentProvider]:
|
|
231
|
+
cls = _registration.require_subclass(loaded, EnvironmentProvider)
|
|
232
|
+
if getattr(cls, "type", None) != name:
|
|
233
|
+
raise TypeError(f"{cls.__qualname__} has type {getattr(cls, 'type', None)!r}; it must equal the entry-point "
|
|
234
|
+
"name, which its records carry as env_provider_type")
|
|
235
|
+
if problem := _registration.unimplemented(cls, EnvironmentProvider):
|
|
236
|
+
raise TypeError(problem)
|
|
237
|
+
return cls
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""The server environment provider: one MCP server in its own container on the configured sandbox provider, with no gateway in front and no state store."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import functools
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from typing import TYPE_CHECKING, ClassVar, Optional
|
|
9
|
+
|
|
10
|
+
import anyio
|
|
11
|
+
|
|
12
|
+
from agent_env.attribution import Attribution
|
|
13
|
+
from agent_env.env.env import DeployedSandboxEnv
|
|
14
|
+
from agent_env.env.gateway import AGENT_ENV_GATEWAY_MCP_PORT
|
|
15
|
+
from agent_env.env.gateway.constants import WELL_KNOWN_PATH
|
|
16
|
+
from agent_env.providers.env_providers.env_provider import _SandboxEnvironmentProvider, _size_kwargs, _tool_names
|
|
17
|
+
from agent_env.providers.sandbox_providers.sandbox_provider import SandboxProvider
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from agent_env.env.env import Env
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EnvironmentServerProvider(_SandboxEnvironmentProvider):
|
|
24
|
+
"""Deploys one MCP server with no gateway in front; the server's own card is the env card."""
|
|
25
|
+
|
|
26
|
+
type: ClassVar[str] = "server"
|
|
27
|
+
record_class = DeployedSandboxEnv
|
|
28
|
+
|
|
29
|
+
async def deploy(
|
|
30
|
+
self,
|
|
31
|
+
env: Env,
|
|
32
|
+
sandbox_provider: SandboxProvider,
|
|
33
|
+
*,
|
|
34
|
+
ttl_seconds: int = 10800,
|
|
35
|
+
disk_size_gb: float = 10,
|
|
36
|
+
cpu: float | None = None,
|
|
37
|
+
memory_mb: int | None = None,
|
|
38
|
+
priority: Optional[int] = None,
|
|
39
|
+
attribution: Optional[Attribution] = None,
|
|
40
|
+
) -> DeployedSandboxEnv:
|
|
41
|
+
"""The env's MCP server in its own container; returns the record once it serves its card and its tools."""
|
|
42
|
+
from agent_env.env.envs.multi_env import MultiEnv
|
|
43
|
+
from agent_env.env.envs.website import WebsiteEnv
|
|
44
|
+
|
|
45
|
+
if isinstance(env, (MultiEnv, WebsiteEnv)):
|
|
46
|
+
raise TypeError(f"env_provider_type '{self.type}' deploys one MCP server, not a {env.type} env")
|
|
47
|
+
attempt = functools.partial(
|
|
48
|
+
self._deploy_server, env, ttl_seconds=ttl_seconds, disk_size_gb=disk_size_gb,
|
|
49
|
+
cpu=cpu, memory_mb=memory_mb, priority=priority, attribution=attribution,
|
|
50
|
+
)
|
|
51
|
+
return await self._run(sandbox_provider, env.id, attempt)
|
|
52
|
+
|
|
53
|
+
async def install_changelog_triggers(self, environment_name: str) -> None:
|
|
54
|
+
"""No state store, so no changelog to install."""
|
|
55
|
+
|
|
56
|
+
async def _deploy_server(
|
|
57
|
+
self,
|
|
58
|
+
env: Env,
|
|
59
|
+
sandbox_provider: SandboxProvider,
|
|
60
|
+
*,
|
|
61
|
+
ttl_seconds: int,
|
|
62
|
+
disk_size_gb: float,
|
|
63
|
+
cpu: float | None,
|
|
64
|
+
memory_mb: int | None,
|
|
65
|
+
priority: Optional[int],
|
|
66
|
+
attribution: Optional[Attribution],
|
|
67
|
+
) -> DeployedSandboxEnv:
|
|
68
|
+
"""One attempt: the server's container, then its card and tools; returns its record."""
|
|
69
|
+
from agent_env.env.envs.mcp_server import MCPServerEnv
|
|
70
|
+
|
|
71
|
+
name = env.environment_name
|
|
72
|
+
server = await sandbox_provider.create_container(
|
|
73
|
+
image_name=env.docker_image_artifact.image_name,
|
|
74
|
+
port=AGENT_ENV_GATEWAY_MCP_PORT,
|
|
75
|
+
env={
|
|
76
|
+
"ENVIRONMENT_NAME": name,
|
|
77
|
+
"MCP_HOST": "0.0.0.0", # IPv4 too: an IPv6-only "::" bind starves tunnels and published ports
|
|
78
|
+
"DATABASE_URL": f"sqlite:////tmp/{name}.sqlite",
|
|
79
|
+
},
|
|
80
|
+
**_size_kwargs(cpu, memory_mb),
|
|
81
|
+
disk_size_gb=disk_size_gb, timeout=ttl_seconds,
|
|
82
|
+
attribution=dict(attribution or {}),
|
|
83
|
+
priority=priority,
|
|
84
|
+
)
|
|
85
|
+
self._container_sandboxes.append(server)
|
|
86
|
+
self._environment_sandboxes[name] = server
|
|
87
|
+
server_url = server.tunnel_urls[AGENT_ENV_GATEWAY_MCP_PORT]
|
|
88
|
+
try:
|
|
89
|
+
async with asyncio.timeout(_READINESS_TIMEOUT_S):
|
|
90
|
+
card = await self._wait_for_tunnel(server_url, timeout=int(_READINESS_TIMEOUT_S))
|
|
91
|
+
except TimeoutError:
|
|
92
|
+
card = None
|
|
93
|
+
if card is None:
|
|
94
|
+
raise RuntimeError(f"'{name}' did not serve its env card at {server_url}{WELL_KNOWN_PATH} in time; "
|
|
95
|
+
"an env deployed without a gateway must serve its own card")
|
|
96
|
+
card_read_at_utc = datetime.now(timezone.utc).isoformat()
|
|
97
|
+
if card["name"] != name:
|
|
98
|
+
raise RuntimeError(f"'{name}' serves a card named '{card['name']}'; "
|
|
99
|
+
"an env deployed without a gateway must serve its card under its environment_name")
|
|
100
|
+
record = DeployedSandboxEnv(
|
|
101
|
+
env_id=env.id,
|
|
102
|
+
env_version=env.version,
|
|
103
|
+
env_provider_type=self.type,
|
|
104
|
+
environment_card_url=f"{server_url}{WELL_KNOWN_PATH}",
|
|
105
|
+
environment_card=card,
|
|
106
|
+
environment_card_read_at_utc=card_read_at_utc,
|
|
107
|
+
sandbox_id=server.sandbox_id,
|
|
108
|
+
sandbox_type=server.type,
|
|
109
|
+
sandbox_ids={MCPServerEnv.type: {name: server.sandbox_id}},
|
|
110
|
+
)
|
|
111
|
+
await _require_tools(name, record.mcp_url)
|
|
112
|
+
return record
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Wall-clock cap on the server's readiness: attempts alone don't bound a tunnel that hangs each request.
|
|
116
|
+
_READINESS_TIMEOUT_S = 180.0
|
|
117
|
+
|
|
118
|
+
_TOOLS_GATE_TIMEOUT_S = 60.0
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
async def _require_tools(environment_name: str, mcp_url: str) -> None:
|
|
122
|
+
"""Fail unless the server at ``mcp_url`` lists at least one MCP tool and no tool name twice."""
|
|
123
|
+
try:
|
|
124
|
+
# anyio re-cancels in cleanup; asyncio.timeout would wait out the MCP client's session-closing DELETE (up to 300 s).
|
|
125
|
+
with anyio.fail_after(_TOOLS_GATE_TIMEOUT_S):
|
|
126
|
+
tools = await _tool_names(mcp_url)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
raise RuntimeError(f"'{environment_name}' did not list its MCP tools at {mcp_url}: {e!r}") from e
|
|
129
|
+
if not tools:
|
|
130
|
+
raise RuntimeError(f"'{environment_name}' lists no MCP tools at {mcp_url}; an env deployed without a gateway must serve at least one")
|
|
131
|
+
duplicates = sorted({t for t in tools if tools.count(t) > 1})
|
|
132
|
+
if duplicates:
|
|
133
|
+
raise RuntimeError(f"'{environment_name}' lists duplicate MCP tool names at {mcp_url}: {', '.join(duplicates)}")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Env-state providers — abstract WHERE an environment's data/state lives"""
|
|
2
|
+
|
|
3
|
+
from agent_env.providers.env_state.env_state_provider import (
|
|
4
|
+
DatabaseStateProvider,
|
|
5
|
+
DEFAULT_STATE_TTL_SECONDS,
|
|
6
|
+
EnvStateInstance,
|
|
7
|
+
EnvStateProvider,
|
|
8
|
+
StateContext,
|
|
9
|
+
acquire_state_for_deploy,
|
|
10
|
+
build_state_provider,
|
|
11
|
+
LOCAL_POSTGRES_STATE_TYPE,
|
|
12
|
+
)
|
|
13
|
+
from agent_env.providers.env_state.local_postgres import (
|
|
14
|
+
LocalPostgresStateContext,
|
|
15
|
+
LocalPostgresStateProvider,
|
|
16
|
+
LocalPostgresStoreSpec,
|
|
17
|
+
)
|
|
18
|
+
from agent_env.providers.env_state.store import (
|
|
19
|
+
ENV_STATE_INSTANCES_COLLECTION,
|
|
20
|
+
EnvStateInstanceStore,
|
|
21
|
+
get_env_state_instance_store,
|
|
22
|
+
register_env_state_instance,
|
|
23
|
+
reset_env_state_instance_store,
|
|
24
|
+
retire_env_state_instance,
|
|
25
|
+
set_env_state_instance_store,
|
|
26
|
+
update_env_state_instance_metadata,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"DatabaseStateProvider",
|
|
31
|
+
"DEFAULT_STATE_TTL_SECONDS",
|
|
32
|
+
"EnvStateInstance",
|
|
33
|
+
"EnvStateProvider",
|
|
34
|
+
"StateContext",
|
|
35
|
+
"acquire_state_for_deploy",
|
|
36
|
+
"build_state_provider",
|
|
37
|
+
"LOCAL_POSTGRES_STATE_TYPE",
|
|
38
|
+
"LocalPostgresStateContext",
|
|
39
|
+
"LocalPostgresStateProvider",
|
|
40
|
+
"LocalPostgresStoreSpec",
|
|
41
|
+
"ENV_STATE_INSTANCES_COLLECTION",
|
|
42
|
+
"EnvStateInstanceStore",
|
|
43
|
+
"get_env_state_instance_store",
|
|
44
|
+
"set_env_state_instance_store",
|
|
45
|
+
"reset_env_state_instance_store",
|
|
46
|
+
"register_env_state_instance",
|
|
47
|
+
"retire_env_state_instance",
|
|
48
|
+
"update_env_state_instance_metadata",
|
|
49
|
+
]
|