plato-sdk-v2 2.4.2__py3-none-any.whl → 2.5.1__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/v1/cli/chronos.py CHANGED
@@ -536,8 +536,8 @@ async def _run_dev_impl(
536
536
  plato_session_id = session.session_id
537
537
  console.print(f"[green]✅ Created Plato session: {plato_session_id}[/green]")
538
538
 
539
- # Add session to config
540
- config_data["plato_session"] = session.dump()
539
+ # Add session to config (convert Pydantic model to dict for JSON serialization)
540
+ config_data["plato_session"] = session.dump().model_dump()
541
541
 
542
542
  # Create Chronos session
543
543
  console.print("[blue]Creating Chronos session...[/blue]")
plato/v1/cli/world.py CHANGED
@@ -62,41 +62,6 @@ def _extract_schema_from_wheel(wheel_path: Path, module_name: str) -> dict | Non
62
62
  return None
63
63
 
64
64
 
65
- def _register_world_schema(
66
- api_url: str,
67
- api_key: str,
68
- package_name: str,
69
- version: str,
70
- schema_data: dict,
71
- ) -> bool:
72
- """Register world schema with plato.so registry."""
73
- import httpx
74
-
75
- try:
76
- with httpx.Client(base_url=api_url, timeout=30.0) as client:
77
- response = client.post(
78
- f"/v2/pypi/worlds/packages/{package_name}/schema",
79
- json={
80
- "version": version,
81
- "config_schema": schema_data,
82
- },
83
- headers={"X-API-Key": api_key},
84
- )
85
-
86
- if response.status_code in (200, 201):
87
- return True
88
- elif response.status_code == 404:
89
- # Endpoint doesn't exist yet - that's ok
90
- console.print("[dim]Schema registration endpoint not available[/dim]")
91
- return False
92
- else:
93
- console.print(f"[yellow]Warning: Schema registration returned {response.status_code}[/yellow]")
94
- return False
95
- except Exception as e:
96
- console.print(f"[yellow]Warning: Could not register schema: {e}[/yellow]")
97
- return False
98
-
99
-
100
65
  @world_app.command(name="publish")
101
66
  def world_publish(
102
67
  path: str = typer.Argument(".", help="Path to the world package directory (default: current directory)"),
@@ -218,11 +183,19 @@ def world_publish(
218
183
  schema_data = _extract_schema_from_wheel(wheel_file, module_name)
219
184
  if schema_data:
220
185
  props = schema_data.get("properties", {})
221
- console.print(f"[green]Schema found:[/green] {len(props)} properties")
186
+ agents = schema_data.get("agents", [])
187
+ secrets = schema_data.get("secrets", [])
188
+ console.print(
189
+ f"[green]Schema found:[/green] {len(props)} properties, {len(agents)} agents, {len(secrets)} secrets"
190
+ )
222
191
  console.print(f"[dim] Properties: {', '.join(props.keys()) if props else 'none'}[/dim]")
192
+ if agents:
193
+ console.print(f"[dim] Agents: {', '.join(a.get('name', '?') for a in agents)}[/dim]")
223
194
  else:
224
- console.print("[yellow]Warning: No schema.json found in wheel[/yellow]")
195
+ console.print("[red]Error: No schema.json found in wheel[/red]")
225
196
  console.print("[dim] Add a hatch build hook to generate schema.json from get_schema()[/dim]")
197
+ console.print("[dim] See: https://docs.plato.so/worlds/publishing#schema-generation[/dim]")
198
+ raise SystemExit(1)
226
199
 
227
200
  if dry_run:
228
201
  console.print("\n[yellow]Dry run - skipping upload[/yellow]")
@@ -272,11 +245,5 @@ def world_publish(
272
245
  console.print(f"[red]Upload error: {e}[/red]")
273
246
  raise typer.Exit(1) from e
274
247
 
275
- # Register schema if we have one
276
- if schema_data:
277
- console.print("\n[cyan]Registering schema...[/cyan]")
278
- if _register_world_schema(api_url, api_key, package_name, version, schema_data):
279
- console.print("[green]Schema registered![/green]")
280
-
281
248
  console.print("\n[bold]Install with:[/bold]")
282
249
  console.print(f" uv add {package_name} --index-url {api_url}/v2/pypi/worlds/simple/")
plato/worlds/__init__.py CHANGED
@@ -52,7 +52,17 @@ from plato.worlds.base import (
52
52
  get_world,
53
53
  register_world,
54
54
  )
55
- from plato.worlds.config import Agent, AgentConfig, CheckpointConfig, Env, EnvConfig, RunConfig, Secret, StateConfig
55
+ from plato.worlds.config import (
56
+ Agent,
57
+ AgentConfig,
58
+ CheckpointConfig,
59
+ Env,
60
+ EnvConfig,
61
+ EnvList,
62
+ RunConfig,
63
+ Secret,
64
+ StateConfig,
65
+ )
56
66
  from plato.worlds.runner import run_world
57
67
 
58
68
  __all__ = [
@@ -72,6 +82,7 @@ __all__ = [
72
82
  "Agent",
73
83
  "Secret",
74
84
  "Env",
85
+ "EnvList",
75
86
  "EnvConfig",
76
87
  # Env types (re-exported from generated models)
77
88
  "EnvFromArtifact",
plato/worlds/base.py CHANGED
@@ -155,11 +155,11 @@ class BaseWorld(ABC, Generic[ConfigT]):
155
155
 
156
156
  @classmethod
157
157
  def get_schema(cls) -> dict:
158
- """Get full schema including world config, agents, and secrets."""
158
+ """Get full schema including world config, agents, secrets, and envs."""
159
159
  config_class = cls.get_config_class()
160
160
  schema = config_class.get_json_schema()
161
161
 
162
- return {
162
+ result = {
163
163
  "$schema": "https://json-schema.org/draft/2020-12/schema",
164
164
  "type": "object",
165
165
  "properties": schema.get("properties", {}),
@@ -169,6 +169,16 @@ class BaseWorld(ABC, Generic[ConfigT]):
169
169
  "envs": schema.get("envs", []),
170
170
  }
171
171
 
172
+ # Include env_list if present (for worlds with arbitrary environment lists)
173
+ if "env_list" in schema:
174
+ result["env_list"] = schema["env_list"]
175
+
176
+ # Include $defs if present (for nested type references)
177
+ if "$defs" in schema:
178
+ result["$defs"] = schema["$defs"]
179
+
180
+ return result
181
+
172
182
  @abstractmethod
173
183
  async def reset(self) -> Observation:
174
184
  """Setup the world and return initial observation.
plato/worlds/config.py CHANGED
@@ -55,7 +55,7 @@ class Secret:
55
55
 
56
56
 
57
57
  class Env:
58
- """Annotation marker for environment fields.
58
+ """Annotation marker for single environment fields.
59
59
 
60
60
  Environments are VMs that run alongside the world's runtime.
61
61
  They can be specified by artifact ID, simulator name, or resource config.
@@ -72,6 +72,24 @@ class Env:
72
72
  self.required = required
73
73
 
74
74
 
75
+ class EnvList:
76
+ """Annotation marker for a list of arbitrary environments.
77
+
78
+ Use this when the world accepts a dynamic list of environments
79
+ rather than named, fixed environment fields.
80
+
81
+ Usage:
82
+ envs: Annotated[
83
+ list[EnvFromSimulator | EnvFromArtifact | EnvFromResource],
84
+ EnvList(description="Environments to create for this task"),
85
+ Field(discriminator="type"),
86
+ ] = []
87
+ """
88
+
89
+ def __init__(self, description: str = ""):
90
+ self.description = description
91
+
92
+
75
93
  class StateConfig(BaseModel):
76
94
  """Configuration for world state persistence.
77
95
 
@@ -92,12 +110,12 @@ class CheckpointConfig(BaseModel):
92
110
  """Configuration for automatic checkpointing during world execution.
93
111
 
94
112
  Attributes:
95
- enabled: Whether to enable automatic checkpoints after steps.
113
+ enabled: Whether to enable automatic checkpoints after steps (default: False).
96
114
  interval: Create checkpoint every N steps (default: 1 = every step).
97
115
  exclude_envs: Environment aliases to exclude from checkpoints (default: ["runtime"]).
98
116
  """
99
117
 
100
- enabled: bool = True
118
+ enabled: bool = False
101
119
  interval: int = 1
102
120
  exclude_envs: list[str] = Field(default_factory=lambda: ["runtime"])
103
121
 
@@ -150,16 +168,16 @@ class RunConfig(BaseModel):
150
168
  model_config = {"extra": "allow"}
151
169
 
152
170
  @classmethod
153
- def get_field_annotations(cls) -> dict[str, Agent | Secret | Env | None]:
154
- """Get Agent/Secret/Env annotations for each field."""
155
- result: dict[str, Agent | Secret | Env | None] = {}
171
+ def get_field_annotations(cls) -> dict[str, Agent | Secret | Env | EnvList | None]:
172
+ """Get Agent/Secret/Env/EnvList annotations for each field."""
173
+ result: dict[str, Agent | Secret | Env | EnvList | None] = {}
156
174
 
157
175
  for field_name, field_info in cls.model_fields.items():
158
176
  marker = None
159
177
 
160
178
  # Pydantic stores Annotated metadata in field_info.metadata
161
179
  for meta in field_info.metadata:
162
- if isinstance(meta, (Agent, Secret, Env)):
180
+ if isinstance(meta, (Agent, Secret, Env, EnvList)):
163
181
  marker = meta
164
182
  break
165
183
 
@@ -182,6 +200,7 @@ class RunConfig(BaseModel):
182
200
  agents = []
183
201
  secrets = []
184
202
  envs = []
203
+ env_list_field: dict | None = None # For EnvList marker (arbitrary envs)
185
204
 
186
205
  # Skip runtime fields
187
206
  runtime_fields = {"session_id", "otel_url", "upload_url", "all_secrets", "plato_session", "checkpoint", "state"}
@@ -228,6 +247,12 @@ class RunConfig(BaseModel):
228
247
  "default": default_value,
229
248
  }
230
249
  )
250
+ elif isinstance(marker, EnvList):
251
+ # Field marked as a list of arbitrary environments
252
+ env_list_field = {
253
+ "name": field_name,
254
+ "description": marker.description,
255
+ }
231
256
  else:
232
257
  world_properties[field_name] = prop_schema
233
258
 
@@ -236,7 +261,7 @@ class RunConfig(BaseModel):
236
261
  r for r in full_schema.get("required", []) if r not in runtime_fields and annotations.get(r) is None
237
262
  ]
238
263
 
239
- return {
264
+ result = {
240
265
  "properties": world_properties,
241
266
  "required": required,
242
267
  "agents": agents,
@@ -244,6 +269,16 @@ class RunConfig(BaseModel):
244
269
  "envs": envs,
245
270
  }
246
271
 
272
+ # Include $defs if present (for nested type references)
273
+ if "$defs" in full_schema:
274
+ result["$defs"] = full_schema["$defs"]
275
+
276
+ # Add env_list if present (for worlds with arbitrary environment lists)
277
+ if env_list_field:
278
+ result["env_list"] = env_list_field
279
+
280
+ return result
281
+
247
282
  def get_envs(self) -> list[EnvConfig]:
248
283
  """Get all environment configurations from this config.
249
284
 
@@ -287,8 +322,13 @@ class RunConfig(BaseModel):
287
322
  # Handle secrets dict -> individual secret fields
288
323
  secrets_dict = data.pop("secrets", {})
289
324
 
290
- # Handle envs dict -> individual env fields
291
- envs_dict = data.pop("envs", {})
325
+ # Check if there's an EnvList field - if so, don't pop envs as a dict
326
+ has_env_list = any(isinstance(m, EnvList) for m in annotations.values())
327
+
328
+ # Handle envs dict -> individual env fields (only for named Env pattern)
329
+ envs_dict: dict = {}
330
+ if not has_env_list:
331
+ envs_dict = data.pop("envs", {})
292
332
 
293
333
  # Merge world_config into top-level
294
334
  parsed.update(world_config)
@@ -310,6 +350,11 @@ class RunConfig(BaseModel):
310
350
  parsed[field_name] = _parse_env_config(env_data)
311
351
  else:
312
352
  parsed[field_name] = env_data
353
+ elif isinstance(marker, EnvList) and field_name in parsed:
354
+ # Parse list of env configs
355
+ env_list = parsed[field_name]
356
+ if isinstance(env_list, list):
357
+ parsed[field_name] = [_parse_env_config(e) if isinstance(e, dict) else e for e in env_list]
313
358
 
314
359
  # Store all secrets for agent use
315
360
  parsed["all_secrets"] = secrets_dict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plato-sdk-v2
3
- Version: 2.4.2
3
+ Version: 2.5.1
4
4
  Summary: Python SDK for the Plato API
5
5
  Author-email: Plato <support@plato.so>
6
6
  License-Expression: MIT
@@ -387,14 +387,14 @@ plato/v1/sync_flow_executor.py,sha256=kgvNYOtA9FHeNfP7qb8ZPUIlTsfIss_Z98W8uX5vec
387
387
  plato/v1/sync_sdk.py,sha256=2sedg1QJiSxr1I3kCyfaLAnlAgHlbblc3QQP_47O30k,25697
388
388
  plato/v1/cli/__init__.py,sha256=om4b7PxgsoI7rEwuQelmQkqPdhMVn53_5qEN8kvksYw,105
389
389
  plato/v1/cli/agent.py,sha256=EbmEKWCMC5DJjmVDrYuwGenhIDgPjie8hdwrDOTSXaY,43766
390
- plato/v1/cli/chronos.py,sha256=VLsvh34R7dusV5BckYTZAIsCBihQkO5cxZDBq8S2x3I,25532
390
+ plato/v1/cli/chronos.py,sha256=uSgvj9uvBElBWP5me30OhH5eFVvFtH8TxvcIPEie3gA,25601
391
391
  plato/v1/cli/main.py,sha256=iKUz6Mu-4-dgr29qOUmDqBaumOCzNQKZsHAalVtaH0Q,6932
392
392
  plato/v1/cli/pm.py,sha256=TIvXBIWFDjr4s1girMMCuvHWQJkjpmsS-igAamddIWE,49746
393
393
  plato/v1/cli/sandbox.py,sha256=jhTney-Pr8bGmWIXOjVIMtZJ7v7uIoRnuh3wfG7weRg,98718
394
394
  plato/v1/cli/ssh.py,sha256=enrf7Y01ZeRIyHDEX0Yt7up5zEe7MCvE9u8SP4Oqiz4,6926
395
395
  plato/v1/cli/utils.py,sha256=ba7Crv4OjDmgCv4SeB8UeZDin-iOdQw_3N6fd-g5XVk,4572
396
396
  plato/v1/cli/verify.py,sha256=7QmQwfOOkr8a51f8xfVIr2zif7wGl2E8HOZTbOaIoV0,20671
397
- plato/v1/cli/world.py,sha256=yBUadOJs1QYm6Jmx_ACDzogybRq5x4B-BnTvGO_ulQk,9757
397
+ plato/v1/cli/world.py,sha256=05onBuwVQOJ0PV9lEIZQkl80SZWeTOtHWEDd49P3Xkk,8709
398
398
  plato/v1/cli/templates/world-runner.Dockerfile,sha256=p59nPCAOUgphSiOpWA3eteRXUWTmZV6n57zn3dWUoYM,932
399
399
  plato/v1/examples/doordash_tasks.py,sha256=8Sz9qx-vTmiOAiCAbrDRvZGsA1qQQBr1KHbxXdjr7OI,23233
400
400
  plato/v1/examples/loadtest.py,sha256=ZsQYNN_fZjE7CbrbVJb4KDc0OLaH7b66iPrEHDhuw0U,5609
@@ -459,12 +459,12 @@ plato/v2/utils/db_cleanup.py,sha256=lnI5lsMHNHpG85Y99MaE4Rzc3618piuzhvH-uXO1zIc,
459
459
  plato/v2/utils/models.py,sha256=PwehSSnIRG-tM3tWL1PzZEH77ZHhIAZ9R0UPs6YknbM,1441
460
460
  plato/v2/utils/proxy_tunnel.py,sha256=8ZTd0jCGSfIHMvSv1fgEyacuISWnGPHLPbDglWroTzY,10463
461
461
  plato/worlds/README.md,sha256=XFOkEA3cNNcrWkk-Cxnsl-zn-y0kvUENKQRSqFKpdqw,5479
462
- plato/worlds/__init__.py,sha256=ALoou3l5lXvs_YZc5eH6HdMHpvhnpzKWqz__aSC1jFc,2152
463
- plato/worlds/base.py,sha256=1O3iKilXlr56mUPVovHY_BjM3S8T57FrotF4895qv5Y,30675
462
+ plato/worlds/__init__.py,sha256=nwuEerEkP2TSfadPiOMcUE3p6u1vhaS7ZxfTh2zNcF8,2217
463
+ plato/worlds/base.py,sha256=8skzeNlufimvXdWBKVAjmgkZ3xGGt0ijRnu6darPxsk,31017
464
464
  plato/worlds/build_hook.py,sha256=KSoW0kqa5b7NyZ7MYOw2qsZ_2FkWuz0M3Ru7AKOP7Qw,3486
465
- plato/worlds/config.py,sha256=a5frj3mt06rSlT25kE-L8Q2b2MTWkR-8cUoBKpC8tG4,11036
465
+ plato/worlds/config.py,sha256=OJtBygnVACQl_kGF8iLofTIk8zMu8tTCNYav6lHdwNI,12874
466
466
  plato/worlds/runner.py,sha256=r9B2BxBae8_dM7y5cJf9xhThp_I1Qvf_tlPq2rs8qC8,4013
467
- plato_sdk_v2-2.4.2.dist-info/METADATA,sha256=1D5uBnPNi-3B2f5qSsi7WVQ0ezOEBNik0xHr5b0-YFc,8652
468
- plato_sdk_v2-2.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
469
- plato_sdk_v2-2.4.2.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
470
- plato_sdk_v2-2.4.2.dist-info/RECORD,,
467
+ plato_sdk_v2-2.5.1.dist-info/METADATA,sha256=0yaSuouj-8F9kczKswZHAKzku3xRaSzCZol_Vf-Nh9I,8652
468
+ plato_sdk_v2-2.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
469
+ plato_sdk_v2-2.5.1.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
470
+ plato_sdk_v2-2.5.1.dist-info/RECORD,,