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,268 @@
|
|
|
1
|
+
"""Loading a group's plugins into its registry, and recording each one that did not take effect.
|
|
2
|
+
|
|
3
|
+
Internal: the public contract is ``agent_env.plugins`` and the README section "Register types
|
|
4
|
+
from an installed package". Each registry calls ``merge`` when it is built, so a group's plugins
|
|
5
|
+
are imported then and not before.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import inspect
|
|
11
|
+
import logging
|
|
12
|
+
from collections.abc import Callable, Collection, Iterator
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from agent_env.config import runtime
|
|
17
|
+
from agent_env.plugins import _report, _requirements
|
|
18
|
+
from agent_env.plugins._discovery import Plugin, discover
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from agent_env.config.runtime import Config, PluginFailures
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
ENVS = "agent_env.envs"
|
|
26
|
+
ARTIFACTS = "agent_env.artifacts"
|
|
27
|
+
TASK_STEPS = "agent_env.task_steps"
|
|
28
|
+
SANDBOX_PROVIDERS = "agent_env.sandbox_providers"
|
|
29
|
+
STATE_PROVIDERS = "agent_env.state_providers"
|
|
30
|
+
ENV_PROVIDERS = "agent_env.env_providers"
|
|
31
|
+
EXPLORER_PLUGINS = "agent_env.explorer_plugins"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class Registrations:
|
|
36
|
+
"""The plugin registrations ``merge`` made in one registry, while that registry is built.
|
|
37
|
+
|
|
38
|
+
Only names a plugin still registers remain: config that takes a name over ``release``s it,
|
|
39
|
+
and a check that fails later ``reject``s it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
registry: dict[str, Any]
|
|
43
|
+
group: str
|
|
44
|
+
added: dict[str, Plugin]
|
|
45
|
+
classes: dict[str, type]
|
|
46
|
+
failures: dict[str, str] # this group's entry in the Config's PluginFailures
|
|
47
|
+
# The code for each entry in ``failures``.
|
|
48
|
+
codes: dict[str, str] = field(default_factory=dict)
|
|
49
|
+
# Config that took a plugin's name over: name -> (config table, impl as written, the class).
|
|
50
|
+
released: dict[str, tuple[str, str, type]] = field(default_factory=dict)
|
|
51
|
+
# Names more than one entry point claims: left out of the registry, and config cannot take them.
|
|
52
|
+
conflicts: dict[str, tuple[Plugin, ...]] = field(default_factory=dict)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def empty(cls) -> Registrations:
|
|
56
|
+
return cls(registry={}, group="", added={}, classes={}, failures={})
|
|
57
|
+
|
|
58
|
+
def __iter__(self) -> Iterator[str]:
|
|
59
|
+
return iter(list(self.added))
|
|
60
|
+
|
|
61
|
+
def plugin(self, name: str) -> Plugin | None:
|
|
62
|
+
"""The plugin that registers ``name``, if one still does."""
|
|
63
|
+
return self.added.get(name)
|
|
64
|
+
|
|
65
|
+
def failed(self, name: str) -> bool:
|
|
66
|
+
"""Whether a plugin registering ``name`` was skipped."""
|
|
67
|
+
return name in self.failures
|
|
68
|
+
|
|
69
|
+
def release(self, name: str, table: str, impl: str, replacement: type) -> bool:
|
|
70
|
+
"""Config's ``[table]`` takes ``name`` over with ``impl``, which loaded as ``replacement``.
|
|
71
|
+
Returns whether a plugin registered it; warns unless ``replacement`` is the plugin's own class."""
|
|
72
|
+
plugin = self.added.pop(name, None)
|
|
73
|
+
if plugin is None:
|
|
74
|
+
return False
|
|
75
|
+
self.released[name] = (table, impl, replacement)
|
|
76
|
+
if self.classes[name] is not replacement:
|
|
77
|
+
logger.warning("[%s] impl %r replaces the class registered by plugin %s", table, impl, plugin)
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
def refuse(self, name: str, where: str) -> bool:
|
|
81
|
+
"""Whether ``name`` is conflicted, so the config at ``where`` naming it is skipped, with a warning."""
|
|
82
|
+
if name not in self.conflicts:
|
|
83
|
+
return False
|
|
84
|
+
logger.warning("%s names %r and was skipped: %s", where, name, self.failures[name])
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
def reject(self, name: str, code: str, reason: str) -> None:
|
|
88
|
+
"""Drop ``name``'s registration, recording ``reason`` as for a load failure."""
|
|
89
|
+
plugin = self.added.pop(name)
|
|
90
|
+
del self.registry[name]
|
|
91
|
+
self.failures[name] = _reason(plugin, reason)
|
|
92
|
+
self.codes[name] = code
|
|
93
|
+
logger.warning("Plugin %s in %s was skipped: %s", plugin, self.group, reason)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def merge(
|
|
97
|
+
registry: dict[str, Any],
|
|
98
|
+
group: str,
|
|
99
|
+
validate: Callable[[str, Any], type],
|
|
100
|
+
*,
|
|
101
|
+
source: Config | None = None,
|
|
102
|
+
entry: Callable[[type], Any] = lambda cls: cls,
|
|
103
|
+
) -> Registrations:
|
|
104
|
+
"""Add ``group``'s plugins to ``registry``, which holds only the built-ins so far.
|
|
105
|
+
|
|
106
|
+
``validate(name, loaded)`` returns the class to register or raises to reject it; ``entry``
|
|
107
|
+
turns it into the registry's value. Every skipped plugin is recorded on ``source``, the
|
|
108
|
+
Config the registry is being built for. A name more than one entry point claims is left out,
|
|
109
|
+
and every other name still loads. An ``InventoryProbe`` keeps what this build registered.
|
|
110
|
+
"""
|
|
111
|
+
added: dict[str, Plugin] = {}
|
|
112
|
+
classes: dict[str, type] = {}
|
|
113
|
+
failures: dict[str, str] = {}
|
|
114
|
+
codes: dict[str, str] = {}
|
|
115
|
+
conflicts: dict[str, tuple[Plugin, ...]] = {}
|
|
116
|
+
probe = source if isinstance(source, InventoryProbe) else None
|
|
117
|
+
for name, claims in discover(group).items():
|
|
118
|
+
if name in registry:
|
|
119
|
+
claimants = "; ".join(str(p) for p, _ in claims)
|
|
120
|
+
failures[name] = _reason(claimants, "clashes with a built-in")
|
|
121
|
+
codes[name] = _report.BUILTIN_NAME
|
|
122
|
+
logger.warning("Plugin %s in %s clashes with a built-in and was skipped", claimants, group)
|
|
123
|
+
continue
|
|
124
|
+
# A claim whose requirements exclude this agent-env cannot load, so it claims nothing.
|
|
125
|
+
usable, unmet_first = [], None
|
|
126
|
+
for plugin, ep in claims:
|
|
127
|
+
if (unmet := _requirements.incompatibility(getattr(ep, "dist", None))) is None:
|
|
128
|
+
usable.append((plugin, ep))
|
|
129
|
+
continue
|
|
130
|
+
logger.warning("Plugin %s in %s was skipped: it %s", plugin, group, unmet)
|
|
131
|
+
unmet_first = unmet_first or _reason(plugin, unmet)
|
|
132
|
+
if not usable:
|
|
133
|
+
failures[name] = unmet_first
|
|
134
|
+
codes[name] = _report.INCOMPATIBLE_CORE
|
|
135
|
+
continue
|
|
136
|
+
if len(usable) > 1:
|
|
137
|
+
conflicts[name] = tuple(p for p, _ in usable)
|
|
138
|
+
failures[name] = conflict_message(name, group, list(conflicts[name]))
|
|
139
|
+
codes[name] = _report.NAME_CONFLICT
|
|
140
|
+
logger.warning("Plugin name %r in %s was skipped: %s", name, group, failures[name])
|
|
141
|
+
continue
|
|
142
|
+
plugin, ep = usable[0]
|
|
143
|
+
code = _report.LOAD_FAILED
|
|
144
|
+
try:
|
|
145
|
+
loaded = ep.load()
|
|
146
|
+
code = _report.INVALID_PLUGIN
|
|
147
|
+
cls = validate(name, loaded)
|
|
148
|
+
except (Exception, SystemExit) as exc:
|
|
149
|
+
failures[name] = _reason(plugin, f"failed to load: {exc!r}")
|
|
150
|
+
codes[name] = code
|
|
151
|
+
logger.warning("Plugin %s in %s failed to load and was skipped: %r", plugin, group, exc)
|
|
152
|
+
continue
|
|
153
|
+
registry[name] = entry(cls)
|
|
154
|
+
added[name] = plugin
|
|
155
|
+
classes[name] = cls
|
|
156
|
+
if not runtime.in_nested_registry_build():
|
|
157
|
+
_record(source).replace(group, failures)
|
|
158
|
+
registrations = Registrations(
|
|
159
|
+
registry=registry, group=group, added=added, classes=classes, failures=failures, codes=codes,
|
|
160
|
+
conflicts=conflicts,
|
|
161
|
+
)
|
|
162
|
+
if probe is not None:
|
|
163
|
+
probe.registrations[group] = registrations
|
|
164
|
+
return registrations
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass(eq=False)
|
|
168
|
+
class InventoryProbe(runtime.Config):
|
|
169
|
+
"""The throwaway Config ``agent_env.plugins.inventory()`` builds registries on: ``merge`` keeps
|
|
170
|
+
each group's Registrations on it."""
|
|
171
|
+
|
|
172
|
+
registrations: dict[str, Registrations] = field(default_factory=dict, repr=False)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def conflict_message(name: str, group: str, claimants: list[Plugin]) -> str:
|
|
176
|
+
"""Why ``name`` is left out of ``group``: every claimant, and how to settle it."""
|
|
177
|
+
listed = "; ".join(str(p) for p in claimants)
|
|
178
|
+
first = claimants[0]
|
|
179
|
+
if first.dist and all((p.dist, p.version) == (first.dist, first.version) for p in claimants):
|
|
180
|
+
# Removing the package removes every claim, so only its author can settle it.
|
|
181
|
+
return (
|
|
182
|
+
f"{first.dist} {first.version} registers {name!r} in {group} {len(claimants)} times: {listed}. "
|
|
183
|
+
"Report it to the package's author."
|
|
184
|
+
)
|
|
185
|
+
return (
|
|
186
|
+
f"{len(claimants)} installed plugins register {name!r} in {group}: {listed}. "
|
|
187
|
+
"Remove all but one with `agent-env plugin remove <package>`."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def typed_validator(
|
|
192
|
+
base: type, builtins: Collection[str] = (), required: Collection[str] = ()
|
|
193
|
+
) -> Callable[[str, Any], type]:
|
|
194
|
+
"""A ``merge`` validator for a group whose classes carry a ``type`` ClassVar: a subclass of
|
|
195
|
+
``base`` with its own ``type``, registered under exactly that name, since documents are
|
|
196
|
+
written and read by it, and implementing what ``unimplemented`` checks."""
|
|
197
|
+
|
|
198
|
+
def validate(name: str, loaded: Any) -> type:
|
|
199
|
+
cls = require_subclass(loaded, base)
|
|
200
|
+
if cls.type == base.type:
|
|
201
|
+
raise TypeError(f"{cls.__qualname__} does not define its own 'type'")
|
|
202
|
+
if cls.type in builtins:
|
|
203
|
+
raise TypeError(f"{cls.__qualname__} inherits the built-in type {cls.type!r}; give it its own type")
|
|
204
|
+
if cls.type != name:
|
|
205
|
+
raise TypeError(f"{cls.__qualname__} has type {cls.type!r}; its entry point must be named {cls.type!r}")
|
|
206
|
+
if problem := unimplemented(cls, base, required):
|
|
207
|
+
raise TypeError(problem)
|
|
208
|
+
return cls
|
|
209
|
+
|
|
210
|
+
return validate
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def require_subclass(loaded: Any, base: type) -> type:
|
|
214
|
+
if not (isinstance(loaded, type) and issubclass(loaded, base)):
|
|
215
|
+
raise TypeError(f"{loaded!r} is not a subclass of {base.__name__}")
|
|
216
|
+
return loaded
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def unimplemented(cls: type, base: type, required: Collection[str] = ()) -> str | None:
|
|
220
|
+
"""What ``cls`` still has to implement to work as a ``base``, if anything: its abstract methods,
|
|
221
|
+
and each of ``required``, a method ``base`` defines only to raise, that it inherits unchanged or
|
|
222
|
+
overrides with a plain function where ``base`` has a classmethod, which is called on the class."""
|
|
223
|
+
missing = set(getattr(cls, "__abstractmethods__", ()))
|
|
224
|
+
unbound = []
|
|
225
|
+
for name in required:
|
|
226
|
+
mine, theirs = inspect.getattr_static(cls, name), inspect.getattr_static(base, name)
|
|
227
|
+
if _function(mine) is _function(theirs):
|
|
228
|
+
missing.add(name)
|
|
229
|
+
elif isinstance(theirs, classmethod) and not isinstance(mine, (classmethod, staticmethod)):
|
|
230
|
+
unbound.append(name)
|
|
231
|
+
problems = []
|
|
232
|
+
if missing:
|
|
233
|
+
names = sorted(missing)
|
|
234
|
+
listed = names[0] if len(names) == 1 else f"{', '.join(names[:-1])} and {names[-1]}"
|
|
235
|
+
problems.append(f"{cls.__qualname__} must implement {listed}")
|
|
236
|
+
problems += [f"{cls.__qualname__}.{name} must be a classmethod" for name in sorted(unbound)]
|
|
237
|
+
return "; ".join(problems) or None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _function(found: Any) -> Any:
|
|
241
|
+
"""``found`` unwrapped from a classmethod or staticmethod."""
|
|
242
|
+
return getattr(found, "__func__", found)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def load_failures(config: Config | None = None) -> dict[str, dict[str, str]]:
|
|
246
|
+
"""Every plugin that did not take effect, by group then name, with the reason.
|
|
247
|
+
|
|
248
|
+
A plugin is listed when it failed to load, validate or construct, clashed with a built-in, or
|
|
249
|
+
claims a name another entry point also claims.
|
|
250
|
+
Covers the registries ``config`` (default: the process Config) has built so far, so build
|
|
251
|
+
them first; a deployment that ships its plugins can then assert this is empty at startup.
|
|
252
|
+
``reset_config()`` starts a new Config, and so a new record.
|
|
253
|
+
"""
|
|
254
|
+
return _record(config).snapshot()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def failure_note(group: str, name: str) -> str:
|
|
258
|
+
"""``" (…)"`` saying why ``name`` is missing from ``group``'s registry, or ``""``."""
|
|
259
|
+
reason = _record(None).reason(group, name)
|
|
260
|
+
return f" ({reason})" if reason else ""
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _record(config: Config | None) -> PluginFailures:
|
|
264
|
+
return (config or runtime.get_config())._plugin_failures
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _reason(registered_by: object, why: str) -> str:
|
|
268
|
+
return f"registered by {registered_by} but {why}"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""The plugin report's format version and reason codes.
|
|
2
|
+
|
|
3
|
+
Internal: the contract is the README section "Plugin report format". A code says why a
|
|
4
|
+
contribution has its status, or why a group, the config or discovery failed. Codes are never
|
|
5
|
+
removed or reused; a new one needs no format bump.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
# The `format_version` of `agent-env plugin list / show / check --json`. Raised only for a change
|
|
11
|
+
# a consumer cannot ignore: a key removed, renamed or retyped, a new status, or a code redefined.
|
|
12
|
+
FORMAT_VERSION = 1
|
|
13
|
+
|
|
14
|
+
LOAD_FAILED = "load-failed"
|
|
15
|
+
INVALID_PLUGIN = "invalid-plugin"
|
|
16
|
+
INCOMPATIBLE_CORE = "incompatible-core"
|
|
17
|
+
BUILTIN_NAME = "builtin-name"
|
|
18
|
+
NAME_CONFLICT = "name-conflict"
|
|
19
|
+
REPLACED_BY_CONFIG = "replaced-by-config"
|
|
20
|
+
ALREADY_ATTACHED = "already-attached"
|
|
21
|
+
NOT_LOADED = "not-loaded"
|
|
22
|
+
STATUS_UNKNOWN = "status-unknown"
|
|
23
|
+
CONFIG_NOT_FOUND = "config-not-found"
|
|
24
|
+
CONFIG_UNREADABLE = "config-unreadable"
|
|
25
|
+
CONFIG_INVALID = "config-invalid"
|
|
26
|
+
GROUP_BUILD_FAILED = "group-build-failed"
|
|
27
|
+
ENTRY_POINTS_UNREADABLE = "entry-points-unreadable"
|
|
28
|
+
QUALIFIED_ONLY = "qualified-only"
|
|
29
|
+
|
|
30
|
+
# Where each code may appear: a contribution status, or "error" for a group, config or discovery error.
|
|
31
|
+
CODES: dict[str, frozenset[str]] = {
|
|
32
|
+
LOAD_FAILED: frozenset({"failed"}),
|
|
33
|
+
INVALID_PLUGIN: frozenset({"failed"}),
|
|
34
|
+
INCOMPATIBLE_CORE: frozenset({"failed"}),
|
|
35
|
+
BUILTIN_NAME: frozenset({"skipped"}),
|
|
36
|
+
NAME_CONFLICT: frozenset({"conflict", "skipped"}),
|
|
37
|
+
REPLACED_BY_CONFIG: frozenset({"replaced"}),
|
|
38
|
+
ALREADY_ATTACHED: frozenset({"active"}),
|
|
39
|
+
NOT_LOADED: frozenset({"unloaded"}),
|
|
40
|
+
STATUS_UNKNOWN: frozenset({"unloaded"}),
|
|
41
|
+
CONFIG_NOT_FOUND: frozenset({"blocked", "error"}),
|
|
42
|
+
CONFIG_UNREADABLE: frozenset({"blocked", "error"}),
|
|
43
|
+
CONFIG_INVALID: frozenset({"blocked", "error"}),
|
|
44
|
+
GROUP_BUILD_FAILED: frozenset({"blocked", "error"}),
|
|
45
|
+
ENTRY_POINTS_UNREADABLE: frozenset({"error"}),
|
|
46
|
+
QUALIFIED_ONLY: frozenset({"active"}),
|
|
47
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Whether an installed plugin's requirements admit the agent-env installed next to it.
|
|
2
|
+
|
|
3
|
+
Internal: the contract is the README section "Plugin compatibility". Reads installed metadata only,
|
|
4
|
+
so it runs before a plugin is imported, and for ``inventory(load=False)``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from packaging.requirements import InvalidRequirement, Requirement
|
|
14
|
+
from packaging.utils import canonicalize_name
|
|
15
|
+
|
|
16
|
+
# What a plugin may constrain: agent-env, and the protocol package it pins exactly.
|
|
17
|
+
CORE = ("agentenv-framework", "agentenv-framework-protocol")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@functools.cache
|
|
21
|
+
def installed(name: str) -> Optional[str]:
|
|
22
|
+
"""The installed version of ``name``, or None without metadata, as for a source tree on sys.path."""
|
|
23
|
+
try:
|
|
24
|
+
return version(name)
|
|
25
|
+
except PackageNotFoundError:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def incompatibility(dist: Any) -> Optional[str]:
|
|
30
|
+
"""Why ``dist``'s requirements on agent-env exclude the installed versions, or None.
|
|
31
|
+
|
|
32
|
+
Only unconditional requirements count: one under an extra, or whose marker is false here, does not."""
|
|
33
|
+
try:
|
|
34
|
+
requires = list(getattr(dist, "requires", None) or ())
|
|
35
|
+
except Exception:
|
|
36
|
+
return None # the metadata is reported as unreadable where it is listed
|
|
37
|
+
unmet = []
|
|
38
|
+
for line in requires:
|
|
39
|
+
try:
|
|
40
|
+
requirement = Requirement(line)
|
|
41
|
+
except InvalidRequirement:
|
|
42
|
+
continue
|
|
43
|
+
name = canonicalize_name(requirement.name)
|
|
44
|
+
if name not in CORE or (requirement.marker is not None and not requirement.marker.evaluate({"extra": ""})):
|
|
45
|
+
continue
|
|
46
|
+
have = installed(name)
|
|
47
|
+
if have is None:
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
met = requirement.specifier.contains(have, prereleases=True)
|
|
51
|
+
except Exception:
|
|
52
|
+
continue
|
|
53
|
+
if not met:
|
|
54
|
+
unmet.append(f"{name}{requirement.specifier} (installed: {have})")
|
|
55
|
+
return f"needs {'; '.join(unmet)}" if unmet else None
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Providers for deploying environments."""
|
|
2
|
+
|
|
3
|
+
from agent_env.providers.env_providers import (
|
|
4
|
+
DeployedGateway, EnvironmentGatewayProvider, EnvironmentProvider, EnvironmentServerProvider, MCPServerConfig, WebsiteConfig,
|
|
5
|
+
build_env_provider,
|
|
6
|
+
)
|
|
7
|
+
from agent_env.env.envs.service_db import DB_MCP_PORT, DB_WEB_PORT
|
|
8
|
+
from agent_env.providers.sandbox_providers.sandbox import Sandbox, VmSandbox
|
|
9
|
+
from agent_env.providers.sandbox_providers.sandbox_provider import (
|
|
10
|
+
SANDBOX_MODE_CONTAINER, SANDBOX_MODE_VM,
|
|
11
|
+
SandboxProvider,
|
|
12
|
+
build_sandbox_provider,
|
|
13
|
+
get_sandbox_provider, set_sandbox_provider, reset_sandbox_provider,
|
|
14
|
+
get_env_sandbox_provider, set_env_sandbox_provider, reset_env_sandbox_provider,
|
|
15
|
+
get_agent_sandbox_provider, set_agent_sandbox_provider, reset_agent_sandbox_provider,
|
|
16
|
+
)
|
|
17
|
+
from agent_env.providers.sandbox_providers.chained_sandbox_provider import ChainedSandboxProvider
|
|
18
|
+
from agent_env.providers.sandbox_providers.local_sandbox import LocalSandbox, LocalSandboxProvider
|
|
19
|
+
from agent_env.providers.sandbox_providers.modal_sandbox import ModalSandbox, ModalSandboxProvider
|
|
20
|
+
from agent_env.providers.sandbox_providers.modal_vm_sandbox import ModalVmSandbox, ModalVmSandboxProvider
|
|
21
|
+
from agent_env.providers.sandbox_providers.e2b import E2BSandbox, E2BSandboxProvider
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ChainedSandboxProvider",
|
|
25
|
+
"DeployedGateway", "EnvironmentGatewayProvider", "EnvironmentProvider", "EnvironmentServerProvider",
|
|
26
|
+
"MCPServerConfig", "DB_WEB_PORT", "DB_MCP_PORT", "Sandbox", "VmSandbox",
|
|
27
|
+
"SANDBOX_MODE_CONTAINER", "SANDBOX_MODE_VM",
|
|
28
|
+
"SandboxProvider", "LocalSandbox", "LocalSandboxProvider",
|
|
29
|
+
"ModalSandbox", "ModalSandboxProvider",
|
|
30
|
+
"ModalVmSandbox", "ModalVmSandboxProvider",
|
|
31
|
+
"E2BSandbox", "E2BSandboxProvider",
|
|
32
|
+
"WebsiteConfig",
|
|
33
|
+
"build_env_provider", "build_sandbox_provider",
|
|
34
|
+
"get_sandbox_provider", "set_sandbox_provider", "reset_sandbox_provider",
|
|
35
|
+
"get_env_sandbox_provider", "set_env_sandbox_provider", "reset_env_sandbox_provider",
|
|
36
|
+
"get_agent_sandbox_provider", "set_agent_sandbox_provider", "reset_agent_sandbox_provider",
|
|
37
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Environment providers: each deploys an env in one topology and hands back its record."""
|
|
2
|
+
|
|
3
|
+
from agent_env.providers.env_providers.env_provider import EnvironmentProvider, build_env_provider
|
|
4
|
+
from agent_env.providers.env_providers.env_gateway_provider import (
|
|
5
|
+
DeployedGateway, EnvironmentGatewayProvider, MCPServerConfig, SidecarConfig, WebsiteConfig,
|
|
6
|
+
)
|
|
7
|
+
from agent_env.providers.env_providers.env_server_provider import EnvironmentServerProvider
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"DeployedGateway", "EnvironmentGatewayProvider", "EnvironmentProvider", "EnvironmentServerProvider", "MCPServerConfig", "SidecarConfig",
|
|
11
|
+
"WebsiteConfig", "build_env_provider",
|
|
12
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""The gateway deployment's layout: its compose service name, VM app dir and compose path, and website names.
|
|
2
|
+
|
|
3
|
+
Imports nothing from agent_env, so modules in the ``agent_env.env`` import chain can use these while
|
|
4
|
+
``agent_env.providers`` is still importing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
GATEWAY_SERVICE_NAME = "gateway"
|
|
8
|
+
GATEWAY_APP_DIR = "/app"
|
|
9
|
+
|
|
10
|
+
AGENT_ENV_WEBSITE_BACKEND_PORT = 8000
|
|
11
|
+
AGENT_ENV_WEBSITE_BACKEND_SUFFIX = "website-backend"
|
|
12
|
+
AGENT_ENV_WEBSITE_FRONTEND_SUFFIX = "website-frontend"
|
|
13
|
+
DOCKER_COMPOSE_PATH = f"{GATEWAY_APP_DIR}/docker-compose.yml"
|