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.
Files changed (158) hide show
  1. plato/__init__.py +7 -6
  2. plato/_generated/__init__.py +1 -1
  3. plato/_generated/api/v1/env/evaluate_session.py +3 -3
  4. plato/_generated/api/v1/env/log_state_mutation.py +4 -4
  5. plato/_generated/api/v1/sandbox/checkpoint_vm.py +3 -3
  6. plato/_generated/api/v1/sandbox/save_vm_snapshot.py +3 -3
  7. plato/_generated/api/v1/sandbox/setup_sandbox.py +8 -8
  8. plato/_generated/api/v1/session/__init__.py +2 -0
  9. plato/_generated/api/v1/session/get_sessions_for_archival.py +100 -0
  10. plato/_generated/api/v1/testcases/__init__.py +6 -2
  11. plato/_generated/api/v1/testcases/get_mutation_groups_for_testcase.py +98 -0
  12. plato/_generated/api/v1/testcases/{get_next_output_testcase_for_scoring.py → get_next_testcase_for_scoring.py} +23 -10
  13. plato/_generated/api/v1/testcases/get_testcase_metadata_for_scoring.py +74 -0
  14. plato/_generated/api/v2/__init__.py +2 -1
  15. plato/_generated/api/v2/jobs/__init__.py +4 -0
  16. plato/_generated/api/v2/jobs/checkpoint.py +3 -3
  17. plato/_generated/api/v2/jobs/disk_snapshot.py +3 -3
  18. plato/_generated/api/v2/jobs/log_for_job.py +4 -39
  19. plato/_generated/api/v2/jobs/make.py +4 -4
  20. plato/_generated/api/v2/jobs/setup_sandbox.py +97 -0
  21. plato/_generated/api/v2/jobs/snapshot.py +3 -3
  22. plato/_generated/api/v2/jobs/snapshot_store.py +91 -0
  23. plato/_generated/api/v2/sessions/__init__.py +4 -0
  24. plato/_generated/api/v2/sessions/checkpoint.py +3 -3
  25. plato/_generated/api/v2/sessions/disk_snapshot.py +3 -3
  26. plato/_generated/api/v2/sessions/evaluate.py +3 -3
  27. plato/_generated/api/v2/sessions/log_job_mutation.py +4 -39
  28. plato/_generated/api/v2/sessions/make.py +4 -4
  29. plato/_generated/api/v2/sessions/setup_sandbox.py +98 -0
  30. plato/_generated/api/v2/sessions/snapshot.py +3 -3
  31. plato/_generated/api/v2/sessions/snapshot_store.py +94 -0
  32. plato/_generated/api/v2/user/__init__.py +7 -0
  33. plato/_generated/api/v2/user/get_current_user.py +76 -0
  34. plato/_generated/models/__init__.py +174 -23
  35. plato/_sims_generator/__init__.py +19 -4
  36. plato/_sims_generator/instruction.py +203 -0
  37. plato/_sims_generator/templates/instruction/helpers.py.jinja +161 -0
  38. plato/_sims_generator/templates/instruction/init.py.jinja +43 -0
  39. plato/agents/__init__.py +107 -517
  40. plato/agents/base.py +145 -0
  41. plato/agents/build.py +61 -0
  42. plato/agents/config.py +160 -0
  43. plato/agents/logging.py +401 -0
  44. plato/agents/runner.py +161 -0
  45. plato/agents/trajectory.py +266 -0
  46. plato/chronos/__init__.py +37 -0
  47. plato/chronos/api/__init__.py +3 -0
  48. plato/chronos/api/agents/__init__.py +13 -0
  49. plato/chronos/api/agents/create_agent.py +63 -0
  50. plato/chronos/api/agents/delete_agent.py +61 -0
  51. plato/chronos/api/agents/get_agent.py +62 -0
  52. plato/chronos/api/agents/get_agent_schema.py +72 -0
  53. plato/chronos/api/agents/get_agent_versions.py +62 -0
  54. plato/chronos/api/agents/list_agents.py +57 -0
  55. plato/chronos/api/agents/lookup_agent.py +74 -0
  56. plato/chronos/api/auth/__init__.py +9 -0
  57. plato/chronos/api/auth/debug_auth_api_auth_debug_get.py +43 -0
  58. plato/chronos/api/auth/get_auth_status_api_auth_status_get.py +61 -0
  59. plato/chronos/api/auth/get_current_user_route_api_auth_me_get.py +60 -0
  60. plato/chronos/api/callback/__init__.py +11 -0
  61. plato/chronos/api/callback/push_agent_logs.py +61 -0
  62. plato/chronos/api/callback/update_agent_status.py +57 -0
  63. plato/chronos/api/callback/upload_artifacts.py +59 -0
  64. plato/chronos/api/callback/upload_logs_zip.py +57 -0
  65. plato/chronos/api/callback/upload_trajectory.py +57 -0
  66. plato/chronos/api/default/__init__.py +7 -0
  67. plato/chronos/api/default/health.py +43 -0
  68. plato/chronos/api/jobs/__init__.py +7 -0
  69. plato/chronos/api/jobs/launch_job.py +63 -0
  70. plato/chronos/api/registry/__init__.py +19 -0
  71. plato/chronos/api/registry/get_agent_schema_api_registry_agents__agent_name__schema_get.py +62 -0
  72. plato/chronos/api/registry/get_agent_versions_api_registry_agents__agent_name__versions_get.py +52 -0
  73. plato/chronos/api/registry/get_world_schema_api_registry_worlds__package_name__schema_get.py +68 -0
  74. plato/chronos/api/registry/get_world_versions_api_registry_worlds__package_name__versions_get.py +52 -0
  75. plato/chronos/api/registry/list_registry_agents_api_registry_agents_get.py +44 -0
  76. plato/chronos/api/registry/list_registry_worlds_api_registry_worlds_get.py +44 -0
  77. plato/chronos/api/runtimes/__init__.py +11 -0
  78. plato/chronos/api/runtimes/create_runtime.py +63 -0
  79. plato/chronos/api/runtimes/delete_runtime.py +61 -0
  80. plato/chronos/api/runtimes/get_runtime.py +62 -0
  81. plato/chronos/api/runtimes/list_runtimes.py +57 -0
  82. plato/chronos/api/runtimes/test_runtime.py +67 -0
  83. plato/chronos/api/secrets/__init__.py +11 -0
  84. plato/chronos/api/secrets/create_secret.py +63 -0
  85. plato/chronos/api/secrets/delete_secret.py +61 -0
  86. plato/chronos/api/secrets/get_secret.py +62 -0
  87. plato/chronos/api/secrets/list_secrets.py +57 -0
  88. plato/chronos/api/secrets/update_secret.py +68 -0
  89. plato/chronos/api/sessions/__init__.py +10 -0
  90. plato/chronos/api/sessions/get_session.py +62 -0
  91. plato/chronos/api/sessions/get_session_logs.py +72 -0
  92. plato/chronos/api/sessions/get_session_logs_download.py +62 -0
  93. plato/chronos/api/sessions/list_sessions.py +57 -0
  94. plato/chronos/api/status/__init__.py +8 -0
  95. plato/chronos/api/status/get_status_api_status_get.py +44 -0
  96. plato/chronos/api/status/get_version_info_api_version_get.py +44 -0
  97. plato/chronos/api/templates/__init__.py +11 -0
  98. plato/chronos/api/templates/create_template.py +63 -0
  99. plato/chronos/api/templates/delete_template.py +61 -0
  100. plato/chronos/api/templates/get_template.py +62 -0
  101. plato/chronos/api/templates/list_templates.py +57 -0
  102. plato/chronos/api/templates/update_template.py +68 -0
  103. plato/chronos/api/trajectories/__init__.py +8 -0
  104. plato/chronos/api/trajectories/get_trajectory.py +62 -0
  105. plato/chronos/api/trajectories/list_trajectories.py +62 -0
  106. plato/chronos/api/worlds/__init__.py +10 -0
  107. plato/chronos/api/worlds/create_world.py +63 -0
  108. plato/chronos/api/worlds/delete_world.py +61 -0
  109. plato/chronos/api/worlds/get_world.py +62 -0
  110. plato/chronos/api/worlds/list_worlds.py +57 -0
  111. plato/chronos/client.py +171 -0
  112. plato/chronos/errors.py +141 -0
  113. plato/chronos/models/__init__.py +647 -0
  114. plato/chronos/py.typed +0 -0
  115. plato/sims/cli.py +299 -123
  116. plato/sims/registry.py +77 -4
  117. plato/v1/cli/agent.py +88 -84
  118. plato/v1/cli/main.py +2 -0
  119. plato/v1/cli/pm.py +441 -119
  120. plato/v1/cli/sandbox.py +747 -191
  121. plato/v1/cli/sim.py +11 -0
  122. plato/v1/cli/verify.py +1269 -0
  123. plato/v1/cli/world.py +3 -0
  124. plato/v1/flow_executor.py +21 -17
  125. plato/v1/models/env.py +11 -11
  126. plato/v1/sdk.py +2 -2
  127. plato/v1/sync_env.py +11 -11
  128. plato/v1/sync_flow_executor.py +21 -17
  129. plato/v1/sync_sdk.py +4 -2
  130. plato/v2/__init__.py +2 -0
  131. plato/v2/async_/environment.py +20 -1
  132. plato/v2/async_/session.py +54 -3
  133. plato/v2/sync/environment.py +2 -1
  134. plato/v2/sync/session.py +52 -2
  135. plato/worlds/README.md +218 -0
  136. plato/worlds/__init__.py +54 -18
  137. plato/worlds/base.py +304 -93
  138. plato/worlds/config.py +239 -73
  139. plato/worlds/runner.py +391 -80
  140. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/METADATA +1 -3
  141. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/RECORD +143 -68
  142. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/entry_points.txt +1 -0
  143. plato/_generated/api/v2/interfaces/__init__.py +0 -27
  144. plato/_generated/api/v2/interfaces/v2_interface_browser_create.py +0 -68
  145. plato/_generated/api/v2/interfaces/v2_interface_cdp_url.py +0 -65
  146. plato/_generated/api/v2/interfaces/v2_interface_click.py +0 -64
  147. plato/_generated/api/v2/interfaces/v2_interface_close.py +0 -59
  148. plato/_generated/api/v2/interfaces/v2_interface_computer_create.py +0 -68
  149. plato/_generated/api/v2/interfaces/v2_interface_cursor.py +0 -64
  150. plato/_generated/api/v2/interfaces/v2_interface_key.py +0 -68
  151. plato/_generated/api/v2/interfaces/v2_interface_screenshot.py +0 -65
  152. plato/_generated/api/v2/interfaces/v2_interface_scroll.py +0 -70
  153. plato/_generated/api/v2/interfaces/v2_interface_type.py +0 -64
  154. plato/world/__init__.py +0 -44
  155. plato/world/base.py +0 -267
  156. plato/world/config.py +0 -139
  157. plato/world/types.py +0 -47
  158. {plato_sdk_v2-2.0.50.dist-info → plato_sdk_v2-2.2.4.dist-info}/WHEEL +0 -0
plato/worlds/config.py CHANGED
@@ -3,120 +3,286 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pathlib import Path
6
- from typing import Any, Generic, TypeVar
6
+ from typing import Any
7
7
 
8
8
  from pydantic import BaseModel, Field
9
9
 
10
+ from plato._generated.models import (
11
+ EnvFromArtifact,
12
+ EnvFromResource,
13
+ EnvFromSimulator,
14
+ )
15
+ from plato.v2.async_.session import SerializedSession
16
+
17
+ # Union type for environment configurations
18
+ EnvConfig = EnvFromArtifact | EnvFromSimulator | EnvFromResource
19
+
10
20
 
11
21
  class AgentConfig(BaseModel):
12
- """Configuration for a single agent slot.
22
+ """Configuration for an agent.
13
23
 
14
24
  Attributes:
15
25
  image: Docker image URI for the agent
16
- config: Agent-specific configuration (passed to the agent)
26
+ config: Agent-specific configuration passed to the agent
17
27
  """
18
28
 
19
29
  image: str
20
30
  config: dict[str, Any] = Field(default_factory=dict)
21
31
 
22
32
 
23
- class WorldConfig(BaseModel):
24
- """World-specific configuration base class.
33
+ class Agent:
34
+ """Annotation marker for agent fields.
35
+
36
+ Usage:
37
+ coder: Annotated[AgentConfig, Agent(description="Coding agent")]
38
+ """
39
+
40
+ def __init__(self, description: str = "", required: bool = True):
41
+ self.description = description
42
+ self.required = required
43
+
44
+
45
+ class Secret:
46
+ """Annotation marker for secret fields.
25
47
 
26
- Subclass this to create typed configurations for your world:
48
+ Usage:
49
+ api_key: Annotated[str | None, Secret(description="API key")] = None
50
+ """
51
+
52
+ def __init__(self, description: str = "", required: bool = False):
53
+ self.description = description
54
+ self.required = required
55
+
56
+
57
+ class Env:
58
+ """Annotation marker for environment fields.
27
59
 
28
- class CodeWorldConfig(WorldConfig):
60
+ Environments are VMs that run alongside the world's runtime.
61
+ They can be specified by artifact ID, simulator name, or resource config.
62
+
63
+ Usage:
64
+ gitea: Annotated[EnvConfig, Env(description="Git server")] = EnvFromArtifact(
65
+ artifact_id="abc123",
66
+ alias="gitea",
67
+ )
68
+ """
69
+
70
+ def __init__(self, description: str = "", required: bool = True):
71
+ self.description = description
72
+ self.required = required
73
+
74
+
75
+ class RunConfig(BaseModel):
76
+ """Base configuration for running a world.
77
+
78
+ Subclass this with your world-specific fields, agents, secrets, and envs:
79
+
80
+ class CodeWorldConfig(RunConfig):
81
+ # World-specific fields
29
82
  repository_url: str
30
- checkout: str = "main"
31
83
  prompt: str
32
84
 
33
- The schema will be auto-generated from the Pydantic model.
85
+ # Agents (typed)
86
+ coder: Annotated[AgentConfig, Agent(description="Coding agent")]
87
+
88
+ # Secrets (typed)
89
+ git_token: Annotated[str | None, Secret(description="GitHub token")] = None
90
+
91
+ # Environments (typed)
92
+ gitea: Annotated[EnvConfig, Env(description="Git server")] = EnvFromArtifact(
93
+ artifact_id="abc123",
94
+ alias="gitea",
95
+ )
96
+
97
+ Attributes:
98
+ session_id: Unique Chronos session identifier
99
+ callback_url: Callback URL for status updates
100
+ plato_session: Serialized Plato session for connecting to existing VM session
34
101
  """
35
102
 
103
+ session_id: str = ""
104
+ callback_url: str = ""
105
+ all_secrets: dict[str, str] = Field(default_factory=dict) # All secrets (world + agent)
106
+
107
+ # Serialized Plato session for connecting to VM and sending heartbeats
108
+ # This is the output of Session.dump() - used to restore session with Session.load()
109
+ plato_session: SerializedSession | None = None
110
+
36
111
  model_config = {"extra": "allow"}
37
112
 
38
- def get(self, key: str, default: Any = None) -> Any:
39
- """Get a config value by key (for backwards compatibility)."""
40
- return getattr(self, key, default)
113
+ @classmethod
114
+ def get_field_annotations(cls) -> dict[str, Agent | Secret | Env | None]:
115
+ """Get Agent/Secret/Env annotations for each field."""
116
+ result: dict[str, Agent | Secret | Env | None] = {}
117
+
118
+ for field_name, field_info in cls.model_fields.items():
119
+ marker = None
120
+
121
+ # Pydantic stores Annotated metadata in field_info.metadata
122
+ for meta in field_info.metadata:
123
+ if isinstance(meta, (Agent, Secret, Env)):
124
+ marker = meta
125
+ break
126
+
127
+ result[field_name] = marker
128
+
129
+ return result
41
130
 
42
131
  @classmethod
43
132
  def get_json_schema(cls) -> dict:
44
- """Get JSON schema for this config class."""
45
- schema = cls.model_json_schema()
46
- # Remove Pydantic-specific keys
47
- schema.pop("title", None)
48
- return schema
133
+ """Get JSON schema with agents, secrets, and envs separated."""
134
+ # Get full Pydantic schema
135
+ full_schema = cls.model_json_schema()
136
+ full_schema.pop("title", None)
49
137
 
138
+ # Separate fields by annotation type
139
+ annotations = cls.get_field_annotations()
140
+ properties = full_schema.get("properties", {})
50
141
 
51
- # Type variable for typed config classes
52
- WorldConfigT = TypeVar("WorldConfigT", bound=WorldConfig)
142
+ world_properties = {}
143
+ agents = []
144
+ secrets = []
145
+ envs = []
53
146
 
147
+ # Skip runtime fields
148
+ runtime_fields = {"session_id", "callback_url", "all_secrets", "plato_session"}
54
149
 
55
- class RunConfig(BaseModel, Generic[WorldConfigT]):
56
- """Full configuration for running a world.
150
+ for field_name, prop_schema in properties.items():
151
+ if field_name in runtime_fields:
152
+ continue
57
153
 
58
- This is passed to the world runner and contains everything
59
- needed to execute a world. Use with a type parameter for typed config access:
154
+ marker = annotations.get(field_name)
60
155
 
61
- config: RunConfig[CodeWorldConfig] = ...
62
- config.world_config.repository_url # type-safe access
156
+ if isinstance(marker, Agent):
157
+ agents.append(
158
+ {
159
+ "name": field_name,
160
+ "description": marker.description,
161
+ "required": marker.required,
162
+ }
163
+ )
164
+ elif isinstance(marker, Secret):
165
+ secrets.append(
166
+ {
167
+ "name": field_name,
168
+ "description": marker.description,
169
+ "required": marker.required,
170
+ }
171
+ )
172
+ elif isinstance(marker, Env):
173
+ # Get default value for this env field
174
+ field_info = cls.model_fields.get(field_name)
175
+ default_value = None
176
+ if field_info and field_info.default is not None:
177
+ # Serialize the default EnvConfig to dict
178
+ default_env = field_info.default
179
+ if hasattr(default_env, "model_dump"):
180
+ default_value = default_env.model_dump()
181
+ elif isinstance(default_env, dict):
182
+ default_value = default_env
63
183
 
64
- Attributes:
65
- world_config: World-specific configuration (typed via generic)
66
- agents: Mapping of slot name to agent configuration
67
- secrets: Secret values (API keys, tokens, etc.)
68
- session_id: Unique session identifier
69
- """
184
+ envs.append(
185
+ {
186
+ "name": field_name,
187
+ "description": marker.description,
188
+ "required": marker.required,
189
+ "default": default_value,
190
+ }
191
+ )
192
+ else:
193
+ world_properties[field_name] = prop_schema
70
194
 
71
- world_config: WorldConfigT = Field(default_factory=WorldConfig) # type: ignore[assignment]
72
- agents: dict[str, AgentConfig] = Field(default_factory=dict)
73
- secrets: dict[str, str] = Field(default_factory=dict)
74
- session_id: str = ""
75
- callback_url: str = "" # URL for agent to push logs back to Chronos
195
+ # Compute required fields (excluding runtime and annotated fields)
196
+ required = [
197
+ r for r in full_schema.get("required", []) if r not in runtime_fields and annotations.get(r) is None
198
+ ]
76
199
 
77
- def get_agent(self, slot_name: str) -> AgentConfig | None:
78
- """Get agent config for a slot."""
79
- return self.agents.get(slot_name)
200
+ return {
201
+ "properties": world_properties,
202
+ "required": required,
203
+ "agents": agents,
204
+ "secrets": secrets,
205
+ "envs": envs,
206
+ }
80
207
 
81
- def get_secret(self, key: str, default: str | None = None) -> str | None:
82
- """Get a secret value."""
83
- return self.secrets.get(key, default)
208
+ def get_envs(self) -> list[EnvConfig]:
209
+ """Get all environment configurations from this config.
84
210
 
85
- @classmethod
86
- def from_file(
87
- cls,
88
- path: str | Path,
89
- config_class: type[WorldConfig] | None = None,
90
- ) -> RunConfig:
91
- """Load run config from a JSON file.
92
-
93
- Args:
94
- path: Path to JSON config file
95
- config_class: Optional WorldConfig subclass to parse world_config into.
96
- Defaults to WorldConfig if not provided.
211
+ Returns:
212
+ List of EnvConfig objects (EnvFromArtifact, EnvFromSimulator, or EnvFromResource)
97
213
  """
214
+ annotations = self.get_field_annotations()
215
+ envs: list[EnvConfig] = []
216
+
217
+ for field_name, marker in annotations.items():
218
+ if isinstance(marker, Env):
219
+ value = getattr(self, field_name, None)
220
+ if value is not None:
221
+ envs.append(value)
222
+
223
+ return envs
224
+
225
+ @classmethod
226
+ def from_file(cls, path: str | Path) -> RunConfig:
227
+ """Load config from a JSON file."""
98
228
  import json
99
229
 
100
230
  path = Path(path)
101
231
  with open(path) as f:
102
232
  data = json.load(f)
103
233
 
104
- # Parse agents into AgentConfig objects
105
- agents = {}
106
- for slot_name, agent_data in data.get("agents", {}).items():
107
- if isinstance(agent_data, dict):
108
- agents[slot_name] = AgentConfig(**agent_data)
109
- else:
110
- agents[slot_name] = AgentConfig(image=str(agent_data))
111
-
112
- # Use provided config class or default to WorldConfig
113
- config_cls = config_class or WorldConfig
114
- world_config = config_cls(**data.get("world_config", {}))
115
-
116
- return cls(
117
- world_config=world_config, # type: ignore[arg-type]
118
- agents=agents,
119
- secrets=data.get("secrets", {}),
120
- session_id=data.get("session_id", ""),
121
- callback_url=data.get("callback_url", ""),
122
- )
234
+ return cls._from_dict(data)
235
+
236
+ @classmethod
237
+ def _from_dict(cls, data: dict) -> RunConfig:
238
+ """Parse config from a dictionary, handling nested structures."""
239
+ annotations = cls.get_field_annotations()
240
+ parsed: dict[str, Any] = {}
241
+
242
+ # Handle world_config if present (for backwards compatibility)
243
+ world_config = data.pop("world_config", {})
244
+
245
+ # Handle agents dict -> individual agent fields
246
+ agents_dict = data.pop("agents", {})
247
+
248
+ # Handle secrets dict -> individual secret fields
249
+ secrets_dict = data.pop("secrets", {})
250
+
251
+ # Handle envs dict -> individual env fields
252
+ envs_dict = data.pop("envs", {})
253
+
254
+ # Merge world_config into top-level
255
+ parsed.update(world_config)
256
+ parsed.update(data)
257
+
258
+ # Map agents dict to typed fields
259
+ for field_name, marker in annotations.items():
260
+ if isinstance(marker, Agent) and field_name in agents_dict:
261
+ agent_data = agents_dict[field_name]
262
+ if isinstance(agent_data, dict):
263
+ parsed[field_name] = AgentConfig(**agent_data)
264
+ else:
265
+ parsed[field_name] = AgentConfig(image=str(agent_data))
266
+ elif isinstance(marker, Secret) and field_name in secrets_dict:
267
+ parsed[field_name] = secrets_dict[field_name]
268
+ elif isinstance(marker, Env) and field_name in envs_dict:
269
+ env_data = envs_dict[field_name]
270
+ if isinstance(env_data, dict):
271
+ parsed[field_name] = _parse_env_config(env_data)
272
+ else:
273
+ parsed[field_name] = env_data
274
+
275
+ # Store all secrets for agent use
276
+ parsed["all_secrets"] = secrets_dict
277
+
278
+ return cls(**parsed)
279
+
280
+
281
+ def _parse_env_config(data: dict) -> EnvConfig:
282
+ """Parse an env config dict into the appropriate type."""
283
+ if "artifact_id" in data:
284
+ return EnvFromArtifact(**data)
285
+ elif "sim_config" in data:
286
+ return EnvFromResource(**data)
287
+ else:
288
+ return EnvFromSimulator(**data)