plato-sdk-v2 2.0.50__py3-none-any.whl → 2.2.4__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.
- plato/__init__.py +7 -6
- plato/_generated/__init__.py +1 -1
- plato/_generated/api/v1/env/evaluate_session.py +3 -3
- plato/_generated/api/v1/env/log_state_mutation.py +4 -4
- plato/_generated/api/v1/sandbox/checkpoint_vm.py +3 -3
- plato/_generated/api/v1/sandbox/save_vm_snapshot.py +3 -3
- plato/_generated/api/v1/sandbox/setup_sandbox.py +8 -8
- plato/_generated/api/v1/session/__init__.py +2 -0
- plato/_generated/api/v1/session/get_sessions_for_archival.py +100 -0
- plato/_generated/api/v1/testcases/__init__.py +6 -2
- plato/_generated/api/v1/testcases/get_mutation_groups_for_testcase.py +98 -0
- plato/_generated/api/v1/testcases/{get_next_output_testcase_for_scoring.py → get_next_testcase_for_scoring.py} +23 -10
- plato/_generated/api/v1/testcases/get_testcase_metadata_for_scoring.py +74 -0
- plato/_generated/api/v2/__init__.py +2 -1
- plato/_generated/api/v2/jobs/__init__.py +4 -0
- plato/_generated/api/v2/jobs/checkpoint.py +3 -3
- plato/_generated/api/v2/jobs/disk_snapshot.py +3 -3
- plato/_generated/api/v2/jobs/log_for_job.py +4 -39
- plato/_generated/api/v2/jobs/make.py +4 -4
- plato/_generated/api/v2/jobs/setup_sandbox.py +97 -0
- plato/_generated/api/v2/jobs/snapshot.py +3 -3
- plato/_generated/api/v2/jobs/snapshot_store.py +91 -0
- plato/_generated/api/v2/sessions/__init__.py +4 -0
- plato/_generated/api/v2/sessions/checkpoint.py +3 -3
- plato/_generated/api/v2/sessions/disk_snapshot.py +3 -3
- plato/_generated/api/v2/sessions/evaluate.py +3 -3
- plato/_generated/api/v2/sessions/log_job_mutation.py +4 -39
- plato/_generated/api/v2/sessions/make.py +4 -4
- plato/_generated/api/v2/sessions/setup_sandbox.py +98 -0
- plato/_generated/api/v2/sessions/snapshot.py +3 -3
- plato/_generated/api/v2/sessions/snapshot_store.py +94 -0
- plato/_generated/api/v2/user/__init__.py +7 -0
- plato/_generated/api/v2/user/get_current_user.py +76 -0
- plato/_generated/models/__init__.py +174 -23
- plato/_sims_generator/__init__.py +19 -4
- plato/_sims_generator/instruction.py +203 -0
- plato/_sims_generator/templates/instruction/helpers.py.jinja +161 -0
- plato/_sims_generator/templates/instruction/init.py.jinja +43 -0
- plato/agents/__init__.py +107 -517
- plato/agents/base.py +145 -0
- plato/agents/build.py +61 -0
- plato/agents/config.py +160 -0
- plato/agents/logging.py +401 -0
- plato/agents/runner.py +161 -0
- plato/agents/trajectory.py +266 -0
- plato/chronos/__init__.py +37 -0
- plato/chronos/api/__init__.py +3 -0
- plato/chronos/api/agents/__init__.py +13 -0
- plato/chronos/api/agents/create_agent.py +63 -0
- plato/chronos/api/agents/delete_agent.py +61 -0
- plato/chronos/api/agents/get_agent.py +62 -0
- plato/chronos/api/agents/get_agent_schema.py +72 -0
- plato/chronos/api/agents/get_agent_versions.py +62 -0
- plato/chronos/api/agents/list_agents.py +57 -0
- plato/chronos/api/agents/lookup_agent.py +74 -0
- plato/chronos/api/auth/__init__.py +9 -0
- plato/chronos/api/auth/debug_auth_api_auth_debug_get.py +43 -0
- plato/chronos/api/auth/get_auth_status_api_auth_status_get.py +61 -0
- plato/chronos/api/auth/get_current_user_route_api_auth_me_get.py +60 -0
- plato/chronos/api/callback/__init__.py +11 -0
- plato/chronos/api/callback/push_agent_logs.py +61 -0
- plato/chronos/api/callback/update_agent_status.py +57 -0
- plato/chronos/api/callback/upload_artifacts.py +59 -0
- plato/chronos/api/callback/upload_logs_zip.py +57 -0
- plato/chronos/api/callback/upload_trajectory.py +57 -0
- plato/chronos/api/default/__init__.py +7 -0
- plato/chronos/api/default/health.py +43 -0
- plato/chronos/api/jobs/__init__.py +7 -0
- plato/chronos/api/jobs/launch_job.py +63 -0
- plato/chronos/api/registry/__init__.py +19 -0
- plato/chronos/api/registry/get_agent_schema_api_registry_agents__agent_name__schema_get.py +62 -0
- plato/chronos/api/registry/get_agent_versions_api_registry_agents__agent_name__versions_get.py +52 -0
- plato/chronos/api/registry/get_world_schema_api_registry_worlds__package_name__schema_get.py +68 -0
- plato/chronos/api/registry/get_world_versions_api_registry_worlds__package_name__versions_get.py +52 -0
- plato/chronos/api/registry/list_registry_agents_api_registry_agents_get.py +44 -0
- plato/chronos/api/registry/list_registry_worlds_api_registry_worlds_get.py +44 -0
- plato/chronos/api/runtimes/__init__.py +11 -0
- plato/chronos/api/runtimes/create_runtime.py +63 -0
- plato/chronos/api/runtimes/delete_runtime.py +61 -0
- plato/chronos/api/runtimes/get_runtime.py +62 -0
- plato/chronos/api/runtimes/list_runtimes.py +57 -0
- plato/chronos/api/runtimes/test_runtime.py +67 -0
- plato/chronos/api/secrets/__init__.py +11 -0
- plato/chronos/api/secrets/create_secret.py +63 -0
- plato/chronos/api/secrets/delete_secret.py +61 -0
- plato/chronos/api/secrets/get_secret.py +62 -0
- plato/chronos/api/secrets/list_secrets.py +57 -0
- plato/chronos/api/secrets/update_secret.py +68 -0
- plato/chronos/api/sessions/__init__.py +10 -0
- plato/chronos/api/sessions/get_session.py +62 -0
- plato/chronos/api/sessions/get_session_logs.py +72 -0
- plato/chronos/api/sessions/get_session_logs_download.py +62 -0
- plato/chronos/api/sessions/list_sessions.py +57 -0
- plato/chronos/api/status/__init__.py +8 -0
- plato/chronos/api/status/get_status_api_status_get.py +44 -0
- plato/chronos/api/status/get_version_info_api_version_get.py +44 -0
- plato/chronos/api/templates/__init__.py +11 -0
- plato/chronos/api/templates/create_template.py +63 -0
- plato/chronos/api/templates/delete_template.py +61 -0
- plato/chronos/api/templates/get_template.py +62 -0
- plato/chronos/api/templates/list_templates.py +57 -0
- plato/chronos/api/templates/update_template.py +68 -0
- plato/chronos/api/trajectories/__init__.py +8 -0
- plato/chronos/api/trajectories/get_trajectory.py +62 -0
- plato/chronos/api/trajectories/list_trajectories.py +62 -0
- plato/chronos/api/worlds/__init__.py +10 -0
- plato/chronos/api/worlds/create_world.py +63 -0
- plato/chronos/api/worlds/delete_world.py +61 -0
- plato/chronos/api/worlds/get_world.py +62 -0
- plato/chronos/api/worlds/list_worlds.py +57 -0
- plato/chronos/client.py +171 -0
- plato/chronos/errors.py +141 -0
- plato/chronos/models/__init__.py +647 -0
- plato/chronos/py.typed +0 -0
- plato/sims/cli.py +299 -123
- plato/sims/registry.py +77 -4
- plato/v1/cli/agent.py +88 -84
- plato/v1/cli/main.py +2 -0
- plato/v1/cli/pm.py +441 -119
- plato/v1/cli/sandbox.py +747 -191
- plato/v1/cli/sim.py +11 -0
- plato/v1/cli/verify.py +1269 -0
- plato/v1/cli/world.py +3 -0
- plato/v1/flow_executor.py +21 -17
- plato/v1/models/env.py +11 -11
- plato/v1/sdk.py +2 -2
- plato/v1/sync_env.py +11 -11
- plato/v1/sync_flow_executor.py +21 -17
- plato/v1/sync_sdk.py +4 -2
- plato/v2/__init__.py +2 -0
- plato/v2/async_/environment.py +20 -1
- plato/v2/async_/session.py +54 -3
- plato/v2/sync/environment.py +2 -1
- plato/v2/sync/session.py +52 -2
- plato/worlds/README.md +218 -0
- plato/worlds/__init__.py +54 -18
- plato/worlds/base.py +304 -93
- plato/worlds/config.py +239 -73
- plato/worlds/runner.py +391 -80
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/METADATA +1 -3
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/RECORD +143 -68
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/entry_points.txt +1 -0
- plato/_generated/api/v2/interfaces/__init__.py +0 -27
- plato/_generated/api/v2/interfaces/v2_interface_browser_create.py +0 -68
- plato/_generated/api/v2/interfaces/v2_interface_cdp_url.py +0 -65
- plato/_generated/api/v2/interfaces/v2_interface_click.py +0 -64
- plato/_generated/api/v2/interfaces/v2_interface_close.py +0 -59
- plato/_generated/api/v2/interfaces/v2_interface_computer_create.py +0 -68
- plato/_generated/api/v2/interfaces/v2_interface_cursor.py +0 -64
- plato/_generated/api/v2/interfaces/v2_interface_key.py +0 -68
- plato/_generated/api/v2/interfaces/v2_interface_screenshot.py +0 -65
- plato/_generated/api/v2/interfaces/v2_interface_scroll.py +0 -70
- plato/_generated/api/v2/interfaces/v2_interface_type.py +0 -64
- plato/world/__init__.py +0 -44
- plato/world/base.py +0 -267
- plato/world/config.py +0 -139
- plato/world/types.py +0 -47
- {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/WHEEL +0 -0
plato/agents/base.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Base agent class and registry for Plato agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, ClassVar, Generic, TypeVar, get_args, get_origin
|
|
10
|
+
|
|
11
|
+
from plato.agents.config import AgentConfig
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Global registry of agents
|
|
16
|
+
_AGENT_REGISTRY: dict[str, type[BaseAgent]] = {}
|
|
17
|
+
|
|
18
|
+
# Type variable for config
|
|
19
|
+
ConfigT = TypeVar("ConfigT", bound=AgentConfig)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register_agent(name: str | None = None):
|
|
23
|
+
"""Decorator to register an agent class.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
@register_agent("openhands")
|
|
27
|
+
class OpenHandsAgent(BaseAgent[OpenHandsConfig]):
|
|
28
|
+
...
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def decorator(cls: type[BaseAgent]) -> type[BaseAgent]:
|
|
32
|
+
agent_name = name or getattr(cls, "name", cls.__name__.lower().replace("agent", ""))
|
|
33
|
+
_AGENT_REGISTRY[agent_name] = cls
|
|
34
|
+
logger.debug(f"Registered agent: {agent_name} -> {cls.__name__}")
|
|
35
|
+
return cls
|
|
36
|
+
|
|
37
|
+
return decorator
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_registered_agents() -> dict[str, type[BaseAgent]]:
|
|
41
|
+
"""Get all registered agents."""
|
|
42
|
+
return _AGENT_REGISTRY.copy()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_agent(name: str) -> type[BaseAgent] | None:
|
|
46
|
+
"""Get an agent by name."""
|
|
47
|
+
return _AGENT_REGISTRY.get(name)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BaseAgent(ABC, Generic[ConfigT]):
|
|
51
|
+
"""Base class for Plato agents.
|
|
52
|
+
|
|
53
|
+
Subclass with a config type parameter for fully typed config access:
|
|
54
|
+
|
|
55
|
+
class OpenHandsConfig(AgentConfig):
|
|
56
|
+
model_name: str = "anthropic/claude-sonnet-4"
|
|
57
|
+
anthropic_api_key: Annotated[str | None, Secret(description="API key")] = None
|
|
58
|
+
|
|
59
|
+
@register_agent("openhands")
|
|
60
|
+
class OpenHandsAgent(BaseAgent[OpenHandsConfig]):
|
|
61
|
+
name = "openhands"
|
|
62
|
+
description = "OpenHands AI software engineer"
|
|
63
|
+
|
|
64
|
+
async def run(self, instruction: str) -> None:
|
|
65
|
+
# self.config is typed as OpenHandsConfig
|
|
66
|
+
model = self.config.model_name
|
|
67
|
+
...
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Class attributes
|
|
71
|
+
name: ClassVar[str] = "base"
|
|
72
|
+
description: ClassVar[str] = ""
|
|
73
|
+
|
|
74
|
+
# Instance attributes
|
|
75
|
+
config: ConfigT
|
|
76
|
+
|
|
77
|
+
def __init__(self) -> None:
|
|
78
|
+
self.logger = logging.getLogger(f"plato.agents.{self.name}")
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def get_config_class(cls) -> type[AgentConfig]:
|
|
82
|
+
"""Get the config class from the generic parameter."""
|
|
83
|
+
for base in getattr(cls, "__orig_bases__", []):
|
|
84
|
+
origin = get_origin(base)
|
|
85
|
+
if origin is BaseAgent:
|
|
86
|
+
args = get_args(base)
|
|
87
|
+
if args and isinstance(args[0], type) and issubclass(args[0], AgentConfig):
|
|
88
|
+
return args[0]
|
|
89
|
+
return AgentConfig
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def get_version(cls) -> str:
|
|
93
|
+
"""Get version from package metadata."""
|
|
94
|
+
import importlib.metadata
|
|
95
|
+
|
|
96
|
+
for pkg_name in [cls.__module__.split(".")[0], f"plato-agent-{cls.name}"]:
|
|
97
|
+
try:
|
|
98
|
+
return importlib.metadata.version(pkg_name)
|
|
99
|
+
except importlib.metadata.PackageNotFoundError:
|
|
100
|
+
continue
|
|
101
|
+
return "0.0.0"
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def get_schema(cls) -> dict:
|
|
105
|
+
"""Get full schema for the agent including config and build schemas."""
|
|
106
|
+
from plato.agents.build import BuildConfig
|
|
107
|
+
|
|
108
|
+
config_class = cls.get_config_class()
|
|
109
|
+
return {
|
|
110
|
+
"config": config_class.get_json_schema(),
|
|
111
|
+
"build": BuildConfig.get_json_schema(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
async def run(self, instruction: str) -> None:
|
|
116
|
+
"""Run the agent with the given instruction.
|
|
117
|
+
|
|
118
|
+
This is the main entry point for agent execution. Implementations should:
|
|
119
|
+
1. Set up the environment using self.config
|
|
120
|
+
2. Execute the agent's core logic
|
|
121
|
+
3. Write trajectory to logs_dir if applicable
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
instruction: The task instruction/prompt for the agent.
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
RuntimeError: If agent execution fails.
|
|
128
|
+
"""
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
async def write_trajectory(self, trajectory: dict[str, Any]) -> None:
|
|
132
|
+
"""Write ATIF trajectory to the logs directory.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
trajectory: ATIF-formatted trajectory dictionary.
|
|
136
|
+
"""
|
|
137
|
+
logs_dir = Path(self.config.logs_dir)
|
|
138
|
+
agent_logs = logs_dir / "agent"
|
|
139
|
+
agent_logs.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
|
|
141
|
+
trajectory_path = agent_logs / "trajectory.json"
|
|
142
|
+
with open(trajectory_path, "w") as f:
|
|
143
|
+
json.dump(trajectory, f, indent=2)
|
|
144
|
+
|
|
145
|
+
self.logger.info(f"Wrote trajectory to {trajectory_path}")
|
plato/agents/build.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Build configuration for Plato agents.
|
|
2
|
+
|
|
3
|
+
Reads [tool.plato.build] from pyproject.toml to configure Docker builds.
|
|
4
|
+
|
|
5
|
+
Example pyproject.toml:
|
|
6
|
+
[tool.plato.build]
|
|
7
|
+
env = { "SOME_BUILD_ARG" = "value" }
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import tomllib
|
|
19
|
+
except ImportError:
|
|
20
|
+
import tomli as tomllib
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BuildConfig(BaseModel):
|
|
24
|
+
"""Docker build configuration for an agent.
|
|
25
|
+
|
|
26
|
+
Read from [tool.plato.build] in pyproject.toml.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
env: dict[str, str] = Field(
|
|
30
|
+
default_factory=dict,
|
|
31
|
+
description="Environment variables to pass as build args",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def to_dict(self) -> dict[str, Any]:
|
|
35
|
+
"""Convert to dict for schema output."""
|
|
36
|
+
return self.model_dump(exclude_defaults=False)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_json_schema(cls) -> dict[str, Any]:
|
|
40
|
+
"""Get JSON schema for build config."""
|
|
41
|
+
schema = cls.model_json_schema()
|
|
42
|
+
schema.pop("title", None)
|
|
43
|
+
return schema
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_pyproject(cls, path: str | Path) -> BuildConfig:
|
|
47
|
+
"""Load build config from pyproject.toml."""
|
|
48
|
+
path = Path(path)
|
|
49
|
+
if path.is_dir():
|
|
50
|
+
path = path / "pyproject.toml"
|
|
51
|
+
|
|
52
|
+
with open(path, "rb") as f:
|
|
53
|
+
data = tomllib.load(f)
|
|
54
|
+
|
|
55
|
+
build_config = data.get("tool", {}).get("plato", {}).get("build", {})
|
|
56
|
+
return cls(**build_config)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_build_config(agent_path: str | Path) -> BuildConfig:
|
|
60
|
+
"""Load build config from an agent directory."""
|
|
61
|
+
return BuildConfig.from_pyproject(agent_path)
|
plato/agents/config.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Typed configuration for Plato agents.
|
|
2
|
+
|
|
3
|
+
Provides base configuration classes that agents extend with their specific fields.
|
|
4
|
+
Secret fields are automatically loaded from environment variables using pydantic-settings.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
from plato.agents import AgentConfig, Secret
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
class OpenHandsConfig(AgentConfig):
|
|
11
|
+
model_name: str = "anthropic/claude-sonnet-4"
|
|
12
|
+
anthropic_api_key: Annotated[str | None, Secret(description="API key")] = None
|
|
13
|
+
|
|
14
|
+
# Secrets auto-loaded from env vars (ANTHROPIC_API_KEY -> anthropic_api_key)
|
|
15
|
+
config = OpenHandsConfig.from_file("/config.json")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from pydantic import Field
|
|
25
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Secret:
|
|
29
|
+
"""Annotation marker for secret fields.
|
|
30
|
+
|
|
31
|
+
Fields annotated with Secret are automatically loaded from environment variables.
|
|
32
|
+
The env var name is the uppercase version of the field name (e.g., api_key -> API_KEY).
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
api_key: Annotated[str, Secret(description="API key")]
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, description: str = "", required: bool = False):
|
|
39
|
+
self.description = description
|
|
40
|
+
self.required = required
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AgentConfig(BaseSettings):
|
|
44
|
+
"""Base configuration for agents.
|
|
45
|
+
|
|
46
|
+
Extends pydantic-settings BaseSettings, so secret fields are automatically loaded
|
|
47
|
+
from environment variables. The env var name is the uppercase field name.
|
|
48
|
+
|
|
49
|
+
Subclass with agent-specific fields:
|
|
50
|
+
|
|
51
|
+
class OpenHandsConfig(AgentConfig):
|
|
52
|
+
model_name: str = "anthropic/claude-sonnet-4"
|
|
53
|
+
anthropic_api_key: Annotated[str | None, Secret(description="API key")] = None
|
|
54
|
+
|
|
55
|
+
# ANTHROPIC_API_KEY env var is automatically loaded into anthropic_api_key
|
|
56
|
+
config = OpenHandsConfig.from_file("/config.json")
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
logs_dir: Directory for agent logs and trajectory output.
|
|
60
|
+
checkpoint_paths: Directories to watch for checkpoint triggers (for workspace tracking).
|
|
61
|
+
checkpoint_debounce_ms: Debounce interval for checkpoints.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
model_config = SettingsConfigDict(
|
|
65
|
+
env_prefix="", # No prefix - ANTHROPIC_API_KEY maps to anthropic_api_key
|
|
66
|
+
extra="allow",
|
|
67
|
+
env_ignore_empty=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
logs_dir: str = "/logs"
|
|
71
|
+
checkpoint_paths: list[str] = Field(default_factory=list)
|
|
72
|
+
checkpoint_debounce_ms: int = 500
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def get_field_secrets(cls) -> dict[str, Secret]:
|
|
76
|
+
"""Get Secret annotations for each field."""
|
|
77
|
+
result: dict[str, Secret] = {}
|
|
78
|
+
|
|
79
|
+
for field_name, field_info in cls.model_fields.items():
|
|
80
|
+
for meta in field_info.metadata:
|
|
81
|
+
if isinstance(meta, Secret):
|
|
82
|
+
result[field_name] = meta
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def get_json_schema(cls) -> dict:
|
|
89
|
+
"""Get JSON schema with secrets separated."""
|
|
90
|
+
full_schema = cls.model_json_schema()
|
|
91
|
+
full_schema.pop("title", None)
|
|
92
|
+
|
|
93
|
+
secrets_map = cls.get_field_secrets()
|
|
94
|
+
properties = full_schema.get("properties", {})
|
|
95
|
+
|
|
96
|
+
config_properties = {}
|
|
97
|
+
secrets = []
|
|
98
|
+
|
|
99
|
+
# Skip internal fields
|
|
100
|
+
internal_fields = {"logs_dir", "checkpoint_paths", "checkpoint_debounce_ms"}
|
|
101
|
+
|
|
102
|
+
for field_name, prop_schema in properties.items():
|
|
103
|
+
if field_name in internal_fields:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if field_name in secrets_map:
|
|
107
|
+
secret = secrets_map[field_name]
|
|
108
|
+
secrets.append(
|
|
109
|
+
{
|
|
110
|
+
"name": field_name,
|
|
111
|
+
"description": secret.description,
|
|
112
|
+
"required": secret.required,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
config_properties[field_name] = prop_schema
|
|
117
|
+
|
|
118
|
+
required = [r for r in full_schema.get("required", []) if r not in internal_fields and r not in secrets_map]
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": config_properties,
|
|
124
|
+
"required": required,
|
|
125
|
+
"secrets": secrets,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def get_secrets_dict(self) -> dict[str, str]:
|
|
129
|
+
"""Extract secret values as a dict for environment variables."""
|
|
130
|
+
secrets_map = self.get_field_secrets()
|
|
131
|
+
result: dict[str, str] = {}
|
|
132
|
+
|
|
133
|
+
for field_name in secrets_map:
|
|
134
|
+
value = getattr(self, field_name, None)
|
|
135
|
+
if value is not None:
|
|
136
|
+
result[field_name] = value
|
|
137
|
+
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
def get_config_dict(self) -> dict[str, Any]:
|
|
141
|
+
"""Extract non-secret config values as a dict."""
|
|
142
|
+
secrets_map = self.get_field_secrets()
|
|
143
|
+
internal_fields = {"logs_dir", "checkpoint_paths", "checkpoint_debounce_ms"}
|
|
144
|
+
|
|
145
|
+
result: dict[str, Any] = {}
|
|
146
|
+
for field_name in self.model_fields:
|
|
147
|
+
if field_name not in secrets_map and field_name not in internal_fields:
|
|
148
|
+
value = getattr(self, field_name, None)
|
|
149
|
+
if value is not None:
|
|
150
|
+
result[field_name] = value
|
|
151
|
+
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def from_file(cls, path: str | Path) -> AgentConfig:
|
|
156
|
+
"""Load config from a JSON file."""
|
|
157
|
+
path = Path(path)
|
|
158
|
+
with open(path) as f:
|
|
159
|
+
data = json.load(f)
|
|
160
|
+
return cls(**data)
|