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,288 @@
|
|
|
1
|
+
"""Task step that applies a task's behavior config to deployed servers.
|
|
2
|
+
|
|
3
|
+
At setup (after the env is deployed + universe loaded, before the agent runs)
|
|
4
|
+
this step invokes each backing MCP server's ``set_*`` config extensions over the
|
|
5
|
+
gateway. Extensions are advertised on the server's ``EnvironmentCard`` (served at
|
|
6
|
+
``/.well-known/agent-env.json``) under ``capabilities.extensions[]`` and invoked
|
|
7
|
+
at their advertised REST endpoint (e.g. ``/agentenv/ext/set_errors``).
|
|
8
|
+
|
|
9
|
+
Config lives on the backing server, not on the gateway itself. Each server's card
|
|
10
|
+
comes from the env card stored on the deployed record, whose extension endpoints the
|
|
11
|
+
gateway has already rewritten to its ``/svc/mcp-{service}`` proxy; a record without a
|
|
12
|
+
stored card reads it live there. The agent never sees the card or these endpoints;
|
|
13
|
+
only this harness step invokes them.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Any, ClassVar, Optional
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
from agentenv_protocol import client as protocol_v1
|
|
23
|
+
|
|
24
|
+
from agent_env.task_step.context import TaskStepContext
|
|
25
|
+
from agent_env.entity_refs import EntityRef
|
|
26
|
+
from agent_env.task_step.task_step import TaskStep, TaskStepDependency
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ApplyServerConfigError(RuntimeError):
|
|
32
|
+
"""Raised when a config directive cannot be applied (bad target / extension
|
|
33
|
+
not advertised / server rejected the args)."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConfigDirective:
|
|
37
|
+
"""A single config extension invocation against one backing server.
|
|
38
|
+
|
|
39
|
+
- ``service``: backing MCP server name (the env's ``environment_name``); the
|
|
40
|
+
gateway proxy key is ``mcp-<service>``.
|
|
41
|
+
- ``uri``: the extension's stable card URI (e.g. ``urn:agentenv:set-errors/v1``).
|
|
42
|
+
This is the discovery key on the card, NOT a fetchable URL.
|
|
43
|
+
- ``args``: the extension's POST body (e.g. ``{"tool_name", "error_rate",
|
|
44
|
+
"error_type"}`` for set_errors).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, service: str, uri: str, args: dict[str, Any]):
|
|
48
|
+
if not isinstance(service, str) or not service:
|
|
49
|
+
raise ValueError("directive.service must be a non-empty string")
|
|
50
|
+
if not isinstance(uri, str) or not uri:
|
|
51
|
+
raise ValueError("directive.uri must be a non-empty string")
|
|
52
|
+
if not isinstance(args, dict):
|
|
53
|
+
raise ValueError("directive.args must be a dict")
|
|
54
|
+
self.service = service
|
|
55
|
+
self.uri = uri
|
|
56
|
+
self.args = args
|
|
57
|
+
|
|
58
|
+
def to_dict(self) -> dict:
|
|
59
|
+
# Dual-write. This feeds the `tasks` and `task_steps`
|
|
60
|
+
# documents — NOT the delivered bundle. The bundle mints its own dict
|
|
61
|
+
# from these attributes in export/exporter.py, so the permanently frozen
|
|
62
|
+
# `{'service': ...}` Harbor applier input is unaffected by the twin here.
|
|
63
|
+
return {"service": self.service, "environment": self.service, "uri": self.uri, "args": self.args}
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, data: dict) -> "ConfigDirective":
|
|
67
|
+
# Reads both spellings. Legacy-first: it is the spelling every
|
|
68
|
+
# SDK version has always written, so it is never the stale half of a pair.
|
|
69
|
+
service = data["service"] if "service" in data else data.get("environment")
|
|
70
|
+
if service is None:
|
|
71
|
+
raise ValueError("directive requires a 'service' (or 'environment') key")
|
|
72
|
+
return cls(service=service, uri=data["uri"], args=data.get("args") or {})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ApplyServerConfigStep(TaskStep):
|
|
76
|
+
type: ClassVar[str] = "apply_server_config"
|
|
77
|
+
entity_refs = (EntityRef.env("env_id"),)
|
|
78
|
+
|
|
79
|
+
# A directive whose ``service`` is this sentinel is a broadcast: at execution
|
|
80
|
+
# it expands to one directive per MCP server in the env (same uri + args).
|
|
81
|
+
# Lets a caller apply an env-wide config (e.g. set_acting_user across every
|
|
82
|
+
# server) without enumerating services itself. Pairs naturally with
|
|
83
|
+
# ``tolerate_unadvertised`` so servers that don't opt in are simply skipped.
|
|
84
|
+
_BROADCAST: ClassVar[str] = "*"
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
id: str,
|
|
89
|
+
version: Optional[int],
|
|
90
|
+
env_id: str,
|
|
91
|
+
directives: list,
|
|
92
|
+
depends_on: Optional[list[TaskStepDependency]] = None,
|
|
93
|
+
fail_task_on_error: bool = True,
|
|
94
|
+
timeout_seconds: int = 30,
|
|
95
|
+
tolerate_unadvertised: bool = False,
|
|
96
|
+
):
|
|
97
|
+
super().__init__(id, version, depends_on=depends_on, fail_task_on_error=fail_task_on_error)
|
|
98
|
+
if not isinstance(env_id, str) or not env_id:
|
|
99
|
+
raise ValueError("env_id must be a non-empty string")
|
|
100
|
+
if not isinstance(directives, list) or not directives:
|
|
101
|
+
raise ValueError("directives must be a non-empty list")
|
|
102
|
+
self.env_id = env_id
|
|
103
|
+
self.directives = [
|
|
104
|
+
d if isinstance(d, ConfigDirective) else ConfigDirective.from_dict(d)
|
|
105
|
+
for d in directives
|
|
106
|
+
]
|
|
107
|
+
self.timeout_seconds = timeout_seconds
|
|
108
|
+
# When True, a directive whose extension isn't advertised on the target
|
|
109
|
+
# server's card is skipped (recorded) instead of failing the step. Use
|
|
110
|
+
# for broadcast directives applied across every service in an env (e.g.
|
|
111
|
+
# set_acting_user), where some services may not opt into the extension.
|
|
112
|
+
# A failing live card read or an extension that IS advertised but rejects
|
|
113
|
+
# the args (e.g. unresolvable persona) still fails loud regardless; a child
|
|
114
|
+
# env missing from the stored card is skipped (no_env_card).
|
|
115
|
+
self.tolerate_unadvertised = tolerate_unadvertised
|
|
116
|
+
|
|
117
|
+
def to_dict(self) -> dict:
|
|
118
|
+
base = super().to_dict()
|
|
119
|
+
base["env_id"] = self.env_id
|
|
120
|
+
base["directives"] = [d.to_dict() for d in self.directives]
|
|
121
|
+
base["timeout_seconds"] = self.timeout_seconds
|
|
122
|
+
base["tolerate_unadvertised"] = self.tolerate_unadvertised
|
|
123
|
+
return base
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_dict(cls, data: dict) -> "ApplyServerConfigStep":
|
|
127
|
+
return cls(
|
|
128
|
+
**cls._base_from_dict(data),
|
|
129
|
+
env_id=data["env_id"],
|
|
130
|
+
directives=data["directives"],
|
|
131
|
+
timeout_seconds=data.get("timeout_seconds", 30),
|
|
132
|
+
tolerate_unadvertised=data.get("tolerate_unadvertised", False),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _expand_directives(self, deployed) -> list[ConfigDirective]:
|
|
136
|
+
"""Expand any broadcast directive (service == ``*``) into one directive
|
|
137
|
+
per MCP server in the env, resolved from the env definition. The env's
|
|
138
|
+
child service names aren't otherwise enumerable here (the deployed card
|
|
139
|
+
list carries no roster), so we read them from ``Env.get`` — matching how
|
|
140
|
+
deploy_env / load_artifact resolve the env. Non-broadcast directives
|
|
141
|
+
pass through untouched."""
|
|
142
|
+
if not any(d.service == self._BROADCAST for d in self.directives):
|
|
143
|
+
return self.directives
|
|
144
|
+
|
|
145
|
+
from agent_env.env.env import Env
|
|
146
|
+
|
|
147
|
+
env = Env.get(deployed.env_id, deployed.env_version)
|
|
148
|
+
environment_names = [
|
|
149
|
+
e.environment_name for e in (getattr(env, "mcp_server_envs", None) or [])
|
|
150
|
+
]
|
|
151
|
+
# A single-server env (a bare MCPServerEnv) exposes `environment_name` and no
|
|
152
|
+
# `mcp_server_envs`; mirror snapshot_env / exporter enumeration and fall
|
|
153
|
+
# back to it, else a broadcast silently matches nothing on single-server envs.
|
|
154
|
+
if not environment_names and getattr(env, "environment_name", None):
|
|
155
|
+
environment_names = [env.environment_name]
|
|
156
|
+
# A broadcast that resolves to zero services would apply nothing while the
|
|
157
|
+
# step reports success: the exact silent misconfig this step prevents. Fail
|
|
158
|
+
# loud (tolerate_unadvertised governs advertisement, not missing servers).
|
|
159
|
+
if not environment_names:
|
|
160
|
+
raise ApplyServerConfigError(
|
|
161
|
+
f"apply_server_config: broadcast directive matched no MCP servers "
|
|
162
|
+
f"in env {self.env_id!r} (env exposes neither mcp_server_envs nor a "
|
|
163
|
+
f"environment_name); nothing to apply, likely a task authoring error"
|
|
164
|
+
)
|
|
165
|
+
expanded: list[ConfigDirective] = []
|
|
166
|
+
for d in self.directives:
|
|
167
|
+
if d.service != self._BROADCAST:
|
|
168
|
+
expanded.append(d)
|
|
169
|
+
continue
|
|
170
|
+
for svc in environment_names:
|
|
171
|
+
expanded.append(ConfigDirective(service=svc, uri=d.uri, args=d.args))
|
|
172
|
+
return expanded
|
|
173
|
+
|
|
174
|
+
async def execute(self, context: TaskStepContext) -> TaskStepContext:
|
|
175
|
+
deployed = next((d for d in context.deployed_envs if d.env_id == self.env_id), None)
|
|
176
|
+
if deployed is None:
|
|
177
|
+
raise ApplyServerConfigError(f"Env '{self.env_id}' not found in context.deployed_envs")
|
|
178
|
+
|
|
179
|
+
# Record each directive in context metadata the moment it's accepted, so a
|
|
180
|
+
# later failure still leaves an accurate audit trail of what the server was
|
|
181
|
+
# actually armed with — a partial apply must not look like a no-op.
|
|
182
|
+
changes = context.metadata.setdefault("server_config_changes", [])
|
|
183
|
+
# Directives skipped because the target server doesn't advertise the
|
|
184
|
+
# extension (only when tolerate_unadvertised). Kept separate from
|
|
185
|
+
# `changes` so the audit trail never shows a skip as an applied change.
|
|
186
|
+
skipped = context.metadata.setdefault("server_config_skipped", [])
|
|
187
|
+
# Expand broadcast (service="*") directives to one-per-env-service.
|
|
188
|
+
# Env.get inside _expand_directives can raise (missing env / version /
|
|
189
|
+
# network), so wrap it to keep the broadcast path's failure contract the
|
|
190
|
+
# same ApplyServerConfigError as the per-directive HTTP calls below.
|
|
191
|
+
try:
|
|
192
|
+
directives = self._expand_directives(deployed)
|
|
193
|
+
except ApplyServerConfigError:
|
|
194
|
+
raise
|
|
195
|
+
except Exception as e:
|
|
196
|
+
raise ApplyServerConfigError(
|
|
197
|
+
f"Failed to expand broadcast directives for env {self.env_id!r}: "
|
|
198
|
+
f"{type(e).__name__}: {e}"
|
|
199
|
+
) from e
|
|
200
|
+
from agent_env.env import legacy_protocol
|
|
201
|
+
from agent_env.env.env import gateway_url_of
|
|
202
|
+
|
|
203
|
+
# One card per service — a task typically sets several directives (e.g. errors on
|
|
204
|
+
# multiple tools) against the same server. A None card marks a child env with no
|
|
205
|
+
# card: a server predating env-card support, one whose card name differs, or one
|
|
206
|
+
# whose card the deploy couldn't read.
|
|
207
|
+
cards: dict[str, tuple[str, Optional[dict]]] = {}
|
|
208
|
+
for directive in directives:
|
|
209
|
+
try:
|
|
210
|
+
if directive.service not in cards:
|
|
211
|
+
cards[directive.service] = await legacy_protocol.child_env_card(
|
|
212
|
+
deployed, gateway_url_of(deployed), directive.service, timeout=self.timeout_seconds
|
|
213
|
+
)
|
|
214
|
+
base_url, card = cards[directive.service]
|
|
215
|
+
if card is None:
|
|
216
|
+
if not self.tolerate_unadvertised:
|
|
217
|
+
raise ApplyServerConfigError(
|
|
218
|
+
f"Service {directive.service!r} has no env card (env={self.env_id}), so {directive.uri!r} "
|
|
219
|
+
f"can't be applied: it serves none, its card isn't named {directive.service!r}, or the deploy couldn't read it"
|
|
220
|
+
)
|
|
221
|
+
logger.warning(
|
|
222
|
+
f"apply_server_config: service {directive.service!r} serves no "
|
|
223
|
+
f"environment card (env={self.env_id}); skipping {directive.uri!r} "
|
|
224
|
+
f"(tolerate_unadvertised)"
|
|
225
|
+
)
|
|
226
|
+
skipped.append({
|
|
227
|
+
"step_id": self.id,
|
|
228
|
+
"env_id": self.env_id,
|
|
229
|
+
"service": directive.service,
|
|
230
|
+
"environment": directive.service,
|
|
231
|
+
"uri": directive.uri,
|
|
232
|
+
"reason": "no_env_card",
|
|
233
|
+
})
|
|
234
|
+
continue
|
|
235
|
+
ext = protocol_v1.find_extension(card, directive.uri)
|
|
236
|
+
if ext is None:
|
|
237
|
+
if self.tolerate_unadvertised:
|
|
238
|
+
logger.warning(
|
|
239
|
+
f"apply_server_config: {directive.uri!r} not advertised on "
|
|
240
|
+
f"service {directive.service!r} (env={self.env_id}); skipping "
|
|
241
|
+
f"(tolerate_unadvertised)"
|
|
242
|
+
)
|
|
243
|
+
skipped.append({
|
|
244
|
+
"step_id": self.id,
|
|
245
|
+
"env_id": self.env_id,
|
|
246
|
+
"service": directive.service,
|
|
247
|
+
"environment": directive.service,
|
|
248
|
+
"uri": directive.uri,
|
|
249
|
+
"reason": "extension_not_advertised",
|
|
250
|
+
})
|
|
251
|
+
continue
|
|
252
|
+
raise ApplyServerConfigError(
|
|
253
|
+
f"Extension {directive.uri!r} not advertised on card for service "
|
|
254
|
+
f"{directive.service!r} (env={self.env_id}). "
|
|
255
|
+
f"Advertised: {[e.get('uri') for e in (card.get('capabilities') or {}).get('extensions') or []]}"
|
|
256
|
+
)
|
|
257
|
+
result = await protocol_v1.invoke_extension(
|
|
258
|
+
base_url, card, directive.uri, params=directive.args,
|
|
259
|
+
timeout=self.timeout_seconds,
|
|
260
|
+
)
|
|
261
|
+
except ApplyServerConfigError:
|
|
262
|
+
raise
|
|
263
|
+
except httpx.HTTPStatusError as e:
|
|
264
|
+
raise ApplyServerConfigError(
|
|
265
|
+
f"Applying {directive.uri!r} on service {directive.service!r} "
|
|
266
|
+
f"(env={self.env_id}) failed: HTTP {e.response.status_code} {e.response.text}"
|
|
267
|
+
) from e
|
|
268
|
+
except Exception as e:
|
|
269
|
+
raise ApplyServerConfigError(
|
|
270
|
+
f"Applying {directive.uri!r} on service {directive.service!r} "
|
|
271
|
+
f"(env={self.env_id}) failed: {type(e).__name__}: {e}"
|
|
272
|
+
) from e
|
|
273
|
+
|
|
274
|
+
logger.info(
|
|
275
|
+
f"apply_server_config: applied {directive.uri} on service={directive.service} "
|
|
276
|
+
f"env={self.env_id} args={directive.args}: {result}"
|
|
277
|
+
)
|
|
278
|
+
changes.append({
|
|
279
|
+
"step_id": self.id,
|
|
280
|
+
"env_id": self.env_id,
|
|
281
|
+
"service": directive.service,
|
|
282
|
+
"environment": directive.service,
|
|
283
|
+
"uri": directive.uri,
|
|
284
|
+
"args": directive.args,
|
|
285
|
+
"result": result,
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
return context
|