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,475 @@
|
|
|
1
|
+
"""Run dispatch + progress — the Task Runner surface.
|
|
2
|
+
|
|
3
|
+
Every route goes through the configured ``[runner]``. Responses use ``workflow_id`` /
|
|
4
|
+
``instance_id`` for the UI's poll loop; under the local runner ``workflow_id`` is a
|
|
5
|
+
locally-minted run id.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import uuid
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
17
|
+
from pydantic import BaseModel, ConfigDict
|
|
18
|
+
|
|
19
|
+
from agent_env.config import get_config, get_runner
|
|
20
|
+
from agent_env.explorer.routers.common import PaginatedResponse, docs
|
|
21
|
+
from agent_env.runner.runner import RunStatus
|
|
22
|
+
from agent_env.store import Filter, Sort
|
|
23
|
+
from agent_env.task.store import TaskStepStatus
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
router = APIRouter(prefix="/api/v1/tasks", tags=["runs"])
|
|
28
|
+
|
|
29
|
+
TASK_INSTANCES_COLLECTION = "task_instances"
|
|
30
|
+
|
|
31
|
+
MAX_RUNS_PER_REQUEST = 100 # cap a single start-runs fan-out
|
|
32
|
+
|
|
33
|
+
RUN_GROUP_STREAM_POLL_SECONDS = 2 # gap between run-group snapshots on the SSE stream
|
|
34
|
+
RUN_GROUP_STREAM_MAX_SNAPSHOTS = 600 # ceiling before a `timeout` event (~20 min at the poll interval)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RunRequest(BaseModel):
|
|
38
|
+
"""One run. ``extra="forbid"`` rejects unknown keys so an unsupported field returns a
|
|
39
|
+
422 rather than being silently dropped."""
|
|
40
|
+
|
|
41
|
+
model_config = ConfigDict(extra="forbid")
|
|
42
|
+
|
|
43
|
+
version: Optional[int] = None
|
|
44
|
+
agent_model: Optional[str] = None
|
|
45
|
+
agent_artifact_id: Optional[str] = None
|
|
46
|
+
metadata: Optional[dict] = None
|
|
47
|
+
# Resume / override controls, threaded into context.metadata for the task step.
|
|
48
|
+
start_step: Optional[int] = None
|
|
49
|
+
context_from_instance_id: Optional[str] = None
|
|
50
|
+
context_json: Optional[dict] = None
|
|
51
|
+
step_overrides: Optional[dict] = None
|
|
52
|
+
overrides: Optional[dict] = None
|
|
53
|
+
project_id: Optional[str] = None
|
|
54
|
+
priority: Optional[int] = None
|
|
55
|
+
# Accepted but ignored: the local explorer gets models from the [model] config, not a
|
|
56
|
+
# per-request key; declared so a client that always sends them isn't rejected.
|
|
57
|
+
litellm_api_key: Optional[str] = None
|
|
58
|
+
litellm_key_id: Optional[str] = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RunResponse(BaseModel):
|
|
62
|
+
workflow_id: str
|
|
63
|
+
instance_id: str
|
|
64
|
+
status: RunStatus
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Keys the runner acts on that a caller could also smuggle through the untyped
|
|
68
|
+
# `metadata` passthrough. Validating only the typed RunRequest fields is not enough:
|
|
69
|
+
# `_run_metadata` copies `body.metadata` verbatim, so every one of these has to be
|
|
70
|
+
# checked on the merged dict, whatever route it arrived by.
|
|
71
|
+
_UNSUPPORTED_METADATA_KEYS = ("context_from_instance_id", "context_json")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _validate_run_metadata(metadata: dict) -> None:
|
|
75
|
+
"""Reject metadata the runner cannot honour, before a run id is handed back.
|
|
76
|
+
|
|
77
|
+
Anything accepted here and then ignored produces the worst outcome available: a run
|
|
78
|
+
that reports its skipped steps as SUCCESS with no data, or one that raises inside
|
|
79
|
+
the worker after submit already returned 200 and so never registers an instance.
|
|
80
|
+
"""
|
|
81
|
+
for key in _UNSUPPORTED_METADATA_KEYS:
|
|
82
|
+
if metadata.get(key) is not None:
|
|
83
|
+
raise HTTPException(
|
|
84
|
+
status_code=501,
|
|
85
|
+
detail=f"{key} is not supported by this runner: a resumed run would "
|
|
86
|
+
"report skipped steps as successful with no data",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
start_step = metadata.get("start_step")
|
|
90
|
+
if start_step is not None:
|
|
91
|
+
if isinstance(start_step, bool) or not isinstance(start_step, int):
|
|
92
|
+
raise HTTPException(
|
|
93
|
+
status_code=422,
|
|
94
|
+
detail=f"start_step must be an integer, got {type(start_step).__name__}",
|
|
95
|
+
)
|
|
96
|
+
if start_step < 0:
|
|
97
|
+
raise HTTPException(status_code=422, detail="start_step must be >= 0")
|
|
98
|
+
|
|
99
|
+
overrides = metadata.get("user_overrides")
|
|
100
|
+
if overrides is not None and not isinstance(overrides, dict):
|
|
101
|
+
raise HTTPException(
|
|
102
|
+
status_code=422,
|
|
103
|
+
detail=f"metadata.user_overrides must be an object, got {type(overrides).__name__}",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _run_metadata(body: "RunRequest") -> dict:
|
|
108
|
+
"""Fold the override fields into the metadata Task.run() carries."""
|
|
109
|
+
metadata = dict(body.metadata or {})
|
|
110
|
+
_validate_run_metadata(metadata) # what the caller smuggled in
|
|
111
|
+
|
|
112
|
+
# Seed from any user_overrides already in metadata — a re-run inherits the prior
|
|
113
|
+
# instance's — so the explicit fields merge into them rather than replacing the lot.
|
|
114
|
+
user_overrides = {**(metadata.get("user_overrides") or {}), **(body.overrides or {})}
|
|
115
|
+
for key in ("project_id", "priority"):
|
|
116
|
+
value = getattr(body, key)
|
|
117
|
+
if value is not None:
|
|
118
|
+
user_overrides[key] = value
|
|
119
|
+
# The steps read `step_params` (TaskStep.step_param_overrides); the request field is
|
|
120
|
+
# named differently. Writing the request name through dropped every override.
|
|
121
|
+
if body.step_overrides is not None:
|
|
122
|
+
user_overrides["step_params"] = body.step_overrides
|
|
123
|
+
if user_overrides:
|
|
124
|
+
metadata["user_overrides"] = user_overrides
|
|
125
|
+
|
|
126
|
+
for key in ("start_step", *_UNSUPPORTED_METADATA_KEYS):
|
|
127
|
+
value = getattr(body, key)
|
|
128
|
+
if value is not None:
|
|
129
|
+
metadata[key] = value
|
|
130
|
+
_validate_run_metadata(metadata) # and what the typed fields added
|
|
131
|
+
return metadata
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _resolve_task_version(task_id: str, version: Optional[int]) -> int:
|
|
135
|
+
"""The concrete version to run; 404 if the task (at ``version``, else latest) is absent."""
|
|
136
|
+
from agent_env.task import Task
|
|
137
|
+
|
|
138
|
+
task = Task.get(task_id, version)
|
|
139
|
+
if task is None:
|
|
140
|
+
label = f"v{version}" if version is not None else "latest"
|
|
141
|
+
raise HTTPException(status_code=404, detail=f"Task {task_id} {label} not found")
|
|
142
|
+
return task.version
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@router.post("/{task_id}/run", response_model=RunResponse)
|
|
146
|
+
async def start_run(task_id: str, body: RunRequest | None = None) -> RunResponse:
|
|
147
|
+
body = body or RunRequest()
|
|
148
|
+
version = _resolve_task_version(task_id, body.version)
|
|
149
|
+
handle = await get_runner().submit(
|
|
150
|
+
task_id, version,
|
|
151
|
+
agent_model=body.agent_model,
|
|
152
|
+
agent_artifact_id=body.agent_artifact_id,
|
|
153
|
+
metadata=_run_metadata(body),
|
|
154
|
+
)
|
|
155
|
+
return RunResponse(workflow_id=handle.run_id, instance_id=handle.instance_id, status=RunStatus.QUEUED)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class StartRunsRequest(RunRequest):
|
|
159
|
+
"""N runs of one task, started as a group (the UI's "Start N Runs")."""
|
|
160
|
+
|
|
161
|
+
count: Optional[int] = None
|
|
162
|
+
seeds: Optional[list[dict]] = None
|
|
163
|
+
# Accepted but ignored, like litellm_api_key above. Runs are submitted
|
|
164
|
+
# sequentially and LocalRunner bounds execution with a semaphore sized once at
|
|
165
|
+
# construction from `[runner.config] workers`, so there is no per-request knob to
|
|
166
|
+
# honour. Declared so the inherited Start-Runs panel isn't rejected by
|
|
167
|
+
# `extra="forbid"`; the panel labels it as ignored.
|
|
168
|
+
concurrency: Optional[int] = None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class RunInfo(BaseModel):
|
|
172
|
+
index: int
|
|
173
|
+
workflow_id: Optional[str] = None
|
|
174
|
+
seed: Optional[dict] = None
|
|
175
|
+
error: Optional[str] = None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class StartRunsResponse(BaseModel):
|
|
179
|
+
run_group_id: str
|
|
180
|
+
task_id: str
|
|
181
|
+
task_version: Optional[int] = None
|
|
182
|
+
total: int
|
|
183
|
+
runs: list[RunInfo]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@router.post("/{task_id}/runs", response_model=StartRunsResponse)
|
|
187
|
+
async def start_runs(task_id: str, body: StartRunsRequest | None = None) -> StartRunsResponse:
|
|
188
|
+
"""Start a group of runs, returning their handles immediately. ``seeds`` wins over
|
|
189
|
+
``count`` when both are given (one run per seed)."""
|
|
190
|
+
body = body or StartRunsRequest()
|
|
191
|
+
version = _resolve_task_version(task_id, body.version) # 404 the whole group if the task is absent
|
|
192
|
+
requested = len(body.seeds) if body.seeds is not None else max(body.count or 1, 1)
|
|
193
|
+
if requested > MAX_RUNS_PER_REQUEST:
|
|
194
|
+
raise HTTPException(status_code=422,
|
|
195
|
+
detail=f"at most {MAX_RUNS_PER_REQUEST} runs per request (got {requested})")
|
|
196
|
+
seeds = body.seeds if body.seeds is not None else [None] * requested
|
|
197
|
+
run_group_id = f"rg-{uuid.uuid4().hex}"
|
|
198
|
+
base_metadata = _run_metadata(body)
|
|
199
|
+
|
|
200
|
+
runs: list[RunInfo] = []
|
|
201
|
+
for index, seed in enumerate(seeds):
|
|
202
|
+
metadata = dict(base_metadata)
|
|
203
|
+
metadata["run_group_id"] = run_group_id
|
|
204
|
+
if seed:
|
|
205
|
+
metadata["seed"] = seed
|
|
206
|
+
try:
|
|
207
|
+
handle = await get_runner().submit(
|
|
208
|
+
task_id, version,
|
|
209
|
+
agent_model=body.agent_model,
|
|
210
|
+
agent_artifact_id=body.agent_artifact_id,
|
|
211
|
+
metadata=metadata,
|
|
212
|
+
)
|
|
213
|
+
runs.append(RunInfo(index=index, workflow_id=handle.run_id, seed=seed))
|
|
214
|
+
except Exception as e: # one bad run must not sink the group
|
|
215
|
+
logger.exception("Run %d of group %s failed to start", index, run_group_id)
|
|
216
|
+
runs.append(RunInfo(index=index, seed=seed, error=f"{type(e).__name__}: {e}"))
|
|
217
|
+
|
|
218
|
+
return StartRunsResponse(
|
|
219
|
+
run_group_id=run_group_id, task_id=task_id, task_version=version,
|
|
220
|
+
total=len(runs), runs=runs,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@router.post("/{task_id}/cancel-run")
|
|
225
|
+
async def cancel_run(task_id: str, workflow_id: str = Query(...)) -> dict:
|
|
226
|
+
canceled = await get_runner().cancel(workflow_id)
|
|
227
|
+
return {"workflow_id": workflow_id, "canceled": canceled}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@router.get("/{task_id}/runs", response_model=PaginatedResponse)
|
|
231
|
+
def list_runs(
|
|
232
|
+
task_id: str,
|
|
233
|
+
limit: int = Query(50, ge=1, le=500),
|
|
234
|
+
offset: int = Query(0, ge=0),
|
|
235
|
+
) -> PaginatedResponse:
|
|
236
|
+
from agent_env.runner import store as run_store
|
|
237
|
+
|
|
238
|
+
records = run_store.list_runs(task_id=task_id, limit=limit, offset=offset)
|
|
239
|
+
total = docs().count("runs", Filter.of(task_id=task_id))
|
|
240
|
+
return PaginatedResponse(
|
|
241
|
+
items=[r.to_dict() for r in records], total=total, limit=limit, offset=offset,
|
|
242
|
+
has_more=offset + len(records) < total,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@router.get("/{task_id}/instances", response_model=PaginatedResponse)
|
|
247
|
+
def list_instances(
|
|
248
|
+
task_id: str,
|
|
249
|
+
task_version: Optional[int] = None,
|
|
250
|
+
limit: int = Query(50, ge=1, le=500),
|
|
251
|
+
offset: int = Query(0, ge=0),
|
|
252
|
+
) -> PaginatedResponse:
|
|
253
|
+
conditions = {"task_id": task_id}
|
|
254
|
+
if task_version is not None:
|
|
255
|
+
conditions["task_version"] = task_version
|
|
256
|
+
filt = Filter.of(**conditions)
|
|
257
|
+
store = docs()
|
|
258
|
+
items = store.query(TASK_INSTANCES_COLLECTION, filt,
|
|
259
|
+
sort=Sort.by("created_at_utc", descending=True),
|
|
260
|
+
limit=limit, offset=offset)
|
|
261
|
+
total = store.count(TASK_INSTANCES_COLLECTION, filt)
|
|
262
|
+
return PaginatedResponse(items=items, total=total, limit=limit, offset=offset,
|
|
263
|
+
has_more=offset + len(items) < total)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@router.get("/{task_id}/instances/{instance_id}")
|
|
267
|
+
def get_instance(task_id: str, instance_id: str) -> dict:
|
|
268
|
+
doc = docs().find_one(TASK_INSTANCES_COLLECTION, Filter.of(instance_id=instance_id))
|
|
269
|
+
if doc is None:
|
|
270
|
+
raise HTTPException(status_code=404, detail=f"instance {instance_id} not found")
|
|
271
|
+
return doc
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# --- run groups -------------------------------------------------------------
|
|
275
|
+
#
|
|
276
|
+
# A run group is the set of runs sharing a run_group_id (stamped into each run's
|
|
277
|
+
# metadata) — a view over the runs collection, not a stored entity.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _all_task_runs(task_id: str) -> list:
|
|
281
|
+
"""Every run of a task, paged so no fixed limit truncates the group aggregation."""
|
|
282
|
+
from agent_env.runner import store as run_store
|
|
283
|
+
|
|
284
|
+
out, offset = [], 0
|
|
285
|
+
while True:
|
|
286
|
+
page = run_store.list_runs(task_id=task_id, limit=500, offset=offset)
|
|
287
|
+
out.extend(page)
|
|
288
|
+
if len(page) < 500:
|
|
289
|
+
return out
|
|
290
|
+
offset += 500
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _group_id_of(record) -> str:
|
|
294
|
+
"""The group a run belongs to.
|
|
295
|
+
|
|
296
|
+
A batch (``POST /runs``) stamps ``metadata.run_group_id`` on every run it starts.
|
|
297
|
+
A single run (``POST /run``, the "Start 1 Run" button) stamps nothing, so it is its
|
|
298
|
+
own group, keyed by its run id. ``list_run_groups`` and ``_instances_for_group``
|
|
299
|
+
MUST agree on this — when only the former applied the fallback, every single run
|
|
300
|
+
produced a group row that the join could never match, so the Rollouts table showed
|
|
301
|
+
the group with "0 runs" and no instance, for every non-batch run.
|
|
302
|
+
"""
|
|
303
|
+
return (record.overrides.get("metadata") or {}).get("run_group_id") or record.run_id
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _instances_for_group(run_group_id: str, task_id: str) -> tuple[list[dict], dict]:
|
|
307
|
+
"""Every run in the group, joined to its task instance for live status, plus the
|
|
308
|
+
per-step ``{"done", "failed"}`` tally the Batch progress strip draws."""
|
|
309
|
+
out: list[dict] = []
|
|
310
|
+
step_counts: dict[str, dict[str, int]] = {}
|
|
311
|
+
for record in _all_task_runs(task_id):
|
|
312
|
+
if _group_id_of(record) != run_group_id:
|
|
313
|
+
continue
|
|
314
|
+
instance = docs().find_one(
|
|
315
|
+
TASK_INSTANCES_COLLECTION, Filter.of(instance_id=record.instance_id)
|
|
316
|
+
) or {}
|
|
317
|
+
# One outcome per step per run. A completion callback that raises records a
|
|
318
|
+
# failure beside the step's success, and the failure is the final verdict.
|
|
319
|
+
outcome: dict[str, str] = {}
|
|
320
|
+
for step in instance.get("completed_steps") or []:
|
|
321
|
+
step_id, status = step.get("step_id"), step.get("status")
|
|
322
|
+
if status not in (TaskStepStatus.SUCCESS, TaskStepStatus.FAILURE):
|
|
323
|
+
continue
|
|
324
|
+
if outcome.get(step_id) != TaskStepStatus.FAILURE:
|
|
325
|
+
outcome[step_id] = status
|
|
326
|
+
for step_id, status in outcome.items():
|
|
327
|
+
counts = step_counts.setdefault(step_id, {"done": 0, "failed": 0})
|
|
328
|
+
counts["done" if status == TaskStepStatus.SUCCESS else "failed"] += 1
|
|
329
|
+
out.append({
|
|
330
|
+
"instance_id": record.instance_id,
|
|
331
|
+
"workflow_id": record.run_id,
|
|
332
|
+
"seed": (record.overrides.get("metadata") or {}).get("seed"),
|
|
333
|
+
# The run record is authoritative for lifecycle; the instance may lag or be absent.
|
|
334
|
+
"status": str(record.status).lower(),
|
|
335
|
+
"created_at_utc": record.created_at_utc,
|
|
336
|
+
"completed_at_utc": record.finished_at_utc,
|
|
337
|
+
"current_step": instance.get("current_step"),
|
|
338
|
+
"total_steps": instance.get("total_steps"),
|
|
339
|
+
})
|
|
340
|
+
return out, step_counts
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# Run-group funnel buckets keyed by RunStatus, so the tally tracks the enum — adding a
|
|
344
|
+
# status is a deliberate edit here, not a silent fall-through. CANCELED folds into
|
|
345
|
+
# "failed"; QUEUED is "provisioning" (accepted, not yet picked up by a worker).
|
|
346
|
+
_FUNNEL_BUCKET: dict[RunStatus, str] = {
|
|
347
|
+
RunStatus.COMPLETED: "completed",
|
|
348
|
+
RunStatus.FAILED: "failed",
|
|
349
|
+
RunStatus.CANCELED: "failed",
|
|
350
|
+
RunStatus.RUNNING: "running",
|
|
351
|
+
RunStatus.QUEUED: "provisioning",
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _group_status(task_id: str, run_group_id: str) -> dict:
|
|
356
|
+
instances, step_counts = _instances_for_group(run_group_id, task_id)
|
|
357
|
+
tally = {"completed": 0, "failed": 0, "running": 0, "provisioning": 0}
|
|
358
|
+
for inst in instances:
|
|
359
|
+
try:
|
|
360
|
+
run_status: Optional[RunStatus] = RunStatus(str(inst["status"]).upper())
|
|
361
|
+
except ValueError:
|
|
362
|
+
run_status = None
|
|
363
|
+
tally[_FUNNEL_BUCKET.get(run_status, "provisioning")] += 1
|
|
364
|
+
return {
|
|
365
|
+
"run_group_id": run_group_id,
|
|
366
|
+
"task_id": task_id,
|
|
367
|
+
"total": len(instances),
|
|
368
|
+
**tally,
|
|
369
|
+
"step_counts": step_counts,
|
|
370
|
+
"instances": instances,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@router.get("/{task_id}/run-groups", response_model=PaginatedResponse)
|
|
375
|
+
def list_run_groups(
|
|
376
|
+
task_id: str,
|
|
377
|
+
task_version: Optional[int] = None,
|
|
378
|
+
limit: int = Query(20, ge=1, le=200),
|
|
379
|
+
offset: int = Query(0, ge=0),
|
|
380
|
+
) -> PaginatedResponse:
|
|
381
|
+
"""Run groups for a task, newest first — the Rollouts table."""
|
|
382
|
+
groups: dict[str, dict] = {}
|
|
383
|
+
for record in _all_task_runs(task_id):
|
|
384
|
+
if task_version is not None and record.task_version != task_version:
|
|
385
|
+
continue
|
|
386
|
+
gid = _group_id_of(record)
|
|
387
|
+
group = groups.setdefault(gid, {
|
|
388
|
+
"run_group_id": gid,
|
|
389
|
+
"task_id": task_id,
|
|
390
|
+
"task_version": record.task_version,
|
|
391
|
+
"created_at_utc": record.created_at_utc,
|
|
392
|
+
"total": 0,
|
|
393
|
+
})
|
|
394
|
+
group["total"] += 1
|
|
395
|
+
group["created_at_utc"] = record.created_at_utc # newest-first input: ends on the oldest
|
|
396
|
+
|
|
397
|
+
ordered = sorted(groups.values(), key=lambda g: g["created_at_utc"] or "", reverse=True)
|
|
398
|
+
page = ordered[offset: offset + limit]
|
|
399
|
+
items = []
|
|
400
|
+
for group in page:
|
|
401
|
+
status = _group_status(task_id, group["run_group_id"])
|
|
402
|
+
# The list row nests the tally under `counts` and names its timestamp
|
|
403
|
+
# `earliest_created_at_utc` — unlike the flat shape /run-groups/{id} returns.
|
|
404
|
+
items.append({
|
|
405
|
+
"run_group_id": group["run_group_id"],
|
|
406
|
+
"task_id": task_id,
|
|
407
|
+
"task_version": group["task_version"],
|
|
408
|
+
"earliest_created_at_utc": group["created_at_utc"],
|
|
409
|
+
"total": status["total"],
|
|
410
|
+
"counts": {
|
|
411
|
+
"completed": status["completed"],
|
|
412
|
+
"failed": status["failed"],
|
|
413
|
+
"running": status["running"],
|
|
414
|
+
"provisioning": status["provisioning"],
|
|
415
|
+
"waiting": 0,
|
|
416
|
+
},
|
|
417
|
+
"step_counts": status["step_counts"],
|
|
418
|
+
"instances": status["instances"],
|
|
419
|
+
})
|
|
420
|
+
return PaginatedResponse(items=items, total=len(ordered), limit=limit, offset=offset,
|
|
421
|
+
has_more=offset + len(items) < len(ordered))
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
@router.get("/{task_id}/run-groups/{run_group_id}")
|
|
425
|
+
def get_run_group(task_id: str, run_group_id: str) -> dict:
|
|
426
|
+
return _group_status(task_id, run_group_id)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
@router.get("/{task_id}/run-groups/{run_group_id}/stream")
|
|
430
|
+
async def stream_run_group(task_id: str, run_group_id: str):
|
|
431
|
+
"""Server-sent snapshots until every run in the group is terminal, ending with a
|
|
432
|
+
``complete`` event."""
|
|
433
|
+
from fastapi.responses import StreamingResponse
|
|
434
|
+
|
|
435
|
+
async def events():
|
|
436
|
+
for _ in range(RUN_GROUP_STREAM_MAX_SNAPSHOTS):
|
|
437
|
+
snapshot = _group_status(task_id, run_group_id)
|
|
438
|
+
if snapshot["total"] == 0:
|
|
439
|
+
yield "event: not_found\ndata: {}\n\n"
|
|
440
|
+
return
|
|
441
|
+
yield f"event: snapshot\ndata: {json.dumps(snapshot)}\n\n"
|
|
442
|
+
if snapshot["completed"] + snapshot["failed"] == snapshot["total"]:
|
|
443
|
+
yield "event: complete\ndata: {}\n\n"
|
|
444
|
+
return
|
|
445
|
+
await asyncio.sleep(RUN_GROUP_STREAM_POLL_SECONDS)
|
|
446
|
+
yield "event: timeout\ndata: {}\n\n"
|
|
447
|
+
|
|
448
|
+
return StreamingResponse(events(), media_type="text/event-stream")
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
@router.get("/{task_id}/instances/{instance_id}/progress")
|
|
452
|
+
def instance_progress(task_id: str, instance_id: str) -> dict:
|
|
453
|
+
"""Step progress for one instance. Polled while a run is in flight."""
|
|
454
|
+
doc = docs().find_one(TASK_INSTANCES_COLLECTION, Filter.of(instance_id=instance_id))
|
|
455
|
+
if doc is None:
|
|
456
|
+
raise HTTPException(status_code=404, detail=f"instance {instance_id} not found")
|
|
457
|
+
# The poller stops on `done`. It also reads `live_step_index`, deliberately not
|
|
458
|
+
# sent: `current_step` counts completed steps rather than indexing them, and a
|
|
459
|
+
# `depends_on` DAG completes out of order, so the count would mark un-started
|
|
460
|
+
# steps done. The component's firstPending fallback handles that correctly.
|
|
461
|
+
status = doc.get("status")
|
|
462
|
+
current_step = doc.get("current_step")
|
|
463
|
+
try:
|
|
464
|
+
# The instance store spells it "cancelled"; RunStatus spells it CANCELED.
|
|
465
|
+
terminal = RunStatus("CANCELED" if status == "cancelled" else str(status).upper()).is_terminal
|
|
466
|
+
except ValueError:
|
|
467
|
+
terminal = False # an unrecognised status keeps polling rather than stalling the view
|
|
468
|
+
return {
|
|
469
|
+
"instance_id": instance_id,
|
|
470
|
+
"status": status,
|
|
471
|
+
"current_step": current_step,
|
|
472
|
+
"total_steps": doc.get("total_steps"),
|
|
473
|
+
"completed_steps": doc.get("completed_steps") or [],
|
|
474
|
+
"done": terminal,
|
|
475
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Entry-point plugins: register types from an installed package.
|
|
2
|
+
|
|
3
|
+
An installed distribution adds a class to a registry by declaring an entry point. The group
|
|
4
|
+
says what kind of thing it is, the entry-point name is the registry key, and the value is the
|
|
5
|
+
class::
|
|
6
|
+
|
|
7
|
+
[project.entry-points."agent_env.envs"]
|
|
8
|
+
browser = "agentenv_browser.env:BrowserEnv"
|
|
9
|
+
|
|
10
|
+
A plugin's own settings live in the config file under ``[plugins.<its distribution name>]``, and
|
|
11
|
+
``settings`` reads them.
|
|
12
|
+
|
|
13
|
+
This package's top level is the public surface: the group names, ``load_failures``, ``settings``,
|
|
14
|
+
and ``inventory`` with the types it returns; its underscored modules are internal. The full contract
|
|
15
|
+
is in the README ("Register types from an installed package", "Plugin settings"), and the codes the
|
|
16
|
+
inventory reports are in "Plugin report format": codes are stable, reason text is not.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from agent_env.config.plugin_tables import settings
|
|
20
|
+
from agent_env.plugins._inventory import (
|
|
21
|
+
Claimant,
|
|
22
|
+
Contribution,
|
|
23
|
+
Diagnostic,
|
|
24
|
+
Distribution,
|
|
25
|
+
Inventory,
|
|
26
|
+
Replacement,
|
|
27
|
+
Status,
|
|
28
|
+
inventory,
|
|
29
|
+
)
|
|
30
|
+
from agent_env.plugins._registration import (
|
|
31
|
+
ARTIFACTS,
|
|
32
|
+
ENV_PROVIDERS,
|
|
33
|
+
ENVS,
|
|
34
|
+
EXPLORER_PLUGINS,
|
|
35
|
+
SANDBOX_PROVIDERS,
|
|
36
|
+
STATE_PROVIDERS,
|
|
37
|
+
TASK_STEPS,
|
|
38
|
+
load_failures,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"ARTIFACTS",
|
|
43
|
+
"ENV_PROVIDERS",
|
|
44
|
+
"ENVS",
|
|
45
|
+
"EXPLORER_PLUGINS",
|
|
46
|
+
"SANDBOX_PROVIDERS",
|
|
47
|
+
"STATE_PROVIDERS",
|
|
48
|
+
"TASK_STEPS",
|
|
49
|
+
"Claimant",
|
|
50
|
+
"Contribution",
|
|
51
|
+
"Diagnostic",
|
|
52
|
+
"Distribution",
|
|
53
|
+
"Inventory",
|
|
54
|
+
"Replacement",
|
|
55
|
+
"Status",
|
|
56
|
+
"inventory",
|
|
57
|
+
"load_failures",
|
|
58
|
+
"settings",
|
|
59
|
+
]
|