plato-sdk-v2 2.8.7__py3-none-any.whl → 2.9.0__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/cli/chronos.py CHANGED
@@ -6,11 +6,13 @@ import asyncio
6
6
  import json
7
7
  import logging
8
8
  import os
9
+ import platform as plat
9
10
  import subprocess
10
11
  import tempfile
11
12
  from pathlib import Path
12
13
  from typing import Annotated
13
14
 
15
+ import httpx
14
16
  import typer
15
17
 
16
18
  from plato.cli.utils import console
@@ -64,8 +66,6 @@ def launch(
64
66
  The config file should contain world.package (required) and optionally world.config,
65
67
  runtime.artifact_id, and tags.
66
68
  """
67
- import httpx
68
-
69
69
  # Set defaults
70
70
  if not chronos_url:
71
71
  chronos_url = "https://chronos.plato.so"
@@ -126,83 +126,9 @@ def launch(
126
126
  raise typer.Exit(1)
127
127
 
128
128
 
129
- @chronos_app.command()
130
- def example(
131
- world: str = typer.Argument(
132
- "structured-execution",
133
- help="World to generate example config for",
134
- ),
135
- output: Path = typer.Option(
136
- None,
137
- "--output",
138
- "-o",
139
- help="Output file path (prints to stdout if not specified)",
140
- ),
141
- ):
142
- """Generate an example job config file.
143
-
144
- Creates a sample JSON configuration for launching Chronos jobs, which can be
145
- customized for your use case.
146
-
147
- Arguments:
148
- world: World type to generate example for (default: "structured-execution")
149
-
150
- Options:
151
- -o, --output: Output file path. If not specified, prints to stdout.
152
-
153
- Available worlds: structured-execution, code-world
154
- """
155
- examples = {
156
- "structured-execution": {
157
- "world_package": "plato-world-structured-execution",
158
- "world_version": "latest",
159
- "world_config": {
160
- "sim_name": "my-sim",
161
- "github_url": "https://github.com/example/repo",
162
- "max_attempts": 3,
163
- "use_backtrack": True,
164
- "skill_runner": {
165
- "image": "claude-code:2.1.5",
166
- "config": {"model_name": "anthropic/claude-sonnet-4-20250514", "max_turns": 100},
167
- },
168
- "plato_api_key": "pk_xxx",
169
- "anthropic_api_key": "sk-ant-xxx",
170
- },
171
- "_comment": "Agents and secrets are embedded directly in world_config",
172
- },
173
- "code-world": {
174
- "world_package": "plato-world-code",
175
- "world_config": {
176
- "task": "Fix the bug in src/main.py",
177
- "repo_url": "https://github.com/example/repo",
178
- "coder": {
179
- "image": "claude-code:latest",
180
- "config": {"model_name": "anthropic/claude-sonnet-4-20250514"},
181
- },
182
- },
183
- "_comment": "world_version is optional - uses latest if not specified",
184
- },
185
- }
186
-
187
- if world not in examples:
188
- console.print(f"[red]❌ Unknown world: {world}[/red]")
189
- console.print(f"Available examples: {list(examples.keys())}")
190
- raise typer.Exit(1)
191
-
192
- example_config = examples[world]
193
- json_output = json.dumps(example_config, indent=2)
194
-
195
- if output:
196
- with open(output, "w") as f:
197
- f.write(json_output)
198
- console.print(f"[green]✅ Example config written to {output}[/green]")
199
- else:
200
- console.print(json_output)
201
-
202
-
203
129
  def _get_world_runner_dockerfile() -> Path:
204
130
  """Get the path to the world runner Dockerfile template."""
205
- return Path(__file__).parent / "templates" / "world-runner.Dockerfile"
131
+ return Path(__file__).parent.parent / "v1" / "cli" / "templates" / "world-runner.Dockerfile"
206
132
 
207
133
 
208
134
  def _build_world_runner_image(platform_override: str | None = None) -> str:
@@ -253,8 +179,6 @@ def _get_docker_platform(override: str | None = None) -> str:
253
179
  if override:
254
180
  return override
255
181
 
256
- import platform as plat
257
-
258
182
  system = plat.system()
259
183
  machine = plat.machine().lower()
260
184
 
@@ -338,8 +262,22 @@ def _build_agent_image(
338
262
  return True
339
263
 
340
264
 
341
- def _extract_agent_images_from_config(config_data: dict) -> list[str]:
342
- """Extract local agent image names from config data."""
265
+ def _extract_agent_name_from_image(image: str) -> str:
266
+ """Extract agent name from image reference (local or full registry path)."""
267
+ # Remove tag if present (e.g., "explorer:1.0.6" -> "explorer")
268
+ image_without_tag = image.split(":")[0]
269
+ # Get the last path component (e.g., "383806609161.dkr.ecr.../agents/plato/explorer" -> "explorer")
270
+ return image_without_tag.split("/")[-1]
271
+
272
+
273
+ def _extract_agent_images_from_config(config_data: dict, extract_all: bool = False) -> list[str]:
274
+ """Extract agent image names from config data.
275
+
276
+ Args:
277
+ config_data: The world configuration data
278
+ extract_all: If True, extract agent names from ALL images (including registry paths).
279
+ If False, only extract from local images (no registry prefix).
280
+ """
343
281
  images = []
344
282
 
345
283
  # Check agents section
@@ -347,9 +285,14 @@ def _extract_agent_images_from_config(config_data: dict) -> list[str]:
347
285
  for agent_config in agents.values():
348
286
  if isinstance(agent_config, dict):
349
287
  image = agent_config.get("image", "")
350
- # Only include local images (no registry prefix)
351
- if image and "/" not in image.split(":")[0]:
352
- name = image.split(":")[0]
288
+ if image:
289
+ if extract_all:
290
+ name = _extract_agent_name_from_image(image)
291
+ elif "/" not in image.split(":")[0]:
292
+ # Only include local images (no registry prefix)
293
+ name = image.split(":")[0]
294
+ else:
295
+ continue
353
296
  if name not in images:
354
297
  images.append(name)
355
298
 
@@ -358,14 +301,44 @@ def _extract_agent_images_from_config(config_data: dict) -> list[str]:
358
301
  agent_config = config_data.get(field, {})
359
302
  if isinstance(agent_config, dict):
360
303
  image = agent_config.get("image", "")
361
- if image and "/" not in image.split(":")[0]:
362
- name = image.split(":")[0]
304
+ if image:
305
+ if extract_all:
306
+ name = _extract_agent_name_from_image(image)
307
+ elif "/" not in image.split(":")[0]:
308
+ name = image.split(":")[0]
309
+ else:
310
+ continue
363
311
  if name not in images:
364
312
  images.append(name)
365
313
 
366
314
  return images
367
315
 
368
316
 
317
+ def _update_config_with_local_images(config_data: dict, built_agents: list[str]) -> dict:
318
+ """Update config to use local image tags for built agents."""
319
+ config_data = config_data.copy()
320
+
321
+ # Update agents section
322
+ if "agents" in config_data:
323
+ config_data["agents"] = config_data["agents"].copy()
324
+ for agent_name, agent_config in config_data["agents"].items():
325
+ if isinstance(agent_config, dict) and "image" in agent_config:
326
+ image_agent_name = _extract_agent_name_from_image(agent_config["image"])
327
+ if image_agent_name in built_agents:
328
+ config_data["agents"][agent_name] = agent_config.copy()
329
+ config_data["agents"][agent_name]["image"] = f"{image_agent_name}:latest"
330
+
331
+ # Update direct fields
332
+ for field in ["coder", "verifier", "skill_runner"]:
333
+ if field in config_data and isinstance(config_data[field], dict) and "image" in config_data[field]:
334
+ image_agent_name = _extract_agent_name_from_image(config_data[field]["image"])
335
+ if image_agent_name in built_agents:
336
+ config_data[field] = config_data[field].copy()
337
+ config_data[field]["image"] = f"{image_agent_name}:latest"
338
+
339
+ return config_data
340
+
341
+
369
342
  async def _create_chronos_session(
370
343
  chronos_url: str,
371
344
  api_key: str,
@@ -375,8 +348,6 @@ async def _create_chronos_session(
375
348
  tags: list[str] | None = None,
376
349
  ) -> dict:
377
350
  """Create a session in Chronos."""
378
- import httpx
379
-
380
351
  url = f"{chronos_url.rstrip('/')}/api/sessions"
381
352
 
382
353
  async with httpx.AsyncClient(timeout=30.0) as client:
@@ -394,25 +365,6 @@ async def _create_chronos_session(
394
365
  return response.json()
395
366
 
396
367
 
397
- async def _close_chronos_session(
398
- chronos_url: str,
399
- api_key: str,
400
- session_id: str,
401
- ) -> None:
402
- """Close a Chronos session."""
403
- import httpx
404
-
405
- url = f"{chronos_url.rstrip('/')}/api/sessions/{session_id}/close"
406
-
407
- try:
408
- async with httpx.AsyncClient(timeout=30.0) as client:
409
- response = await client.post(url, headers={"x-api-key": api_key})
410
- response.raise_for_status()
411
- logger.info(f"Closed Chronos session: {session_id}")
412
- except Exception as e:
413
- logger.warning(f"Failed to close Chronos session: {e}")
414
-
415
-
416
368
  async def _complete_chronos_session(
417
369
  chronos_url: str,
418
370
  api_key: str,
@@ -422,7 +374,6 @@ async def _complete_chronos_session(
422
374
  error_message: str | None = None,
423
375
  ) -> None:
424
376
  """Complete a Chronos session with final status."""
425
- import httpx
426
377
 
427
378
  url = f"{chronos_url.rstrip('/')}/api/sessions/{session_id}/complete"
428
379
 
@@ -493,15 +444,29 @@ async def _run_dev_impl(
493
444
  world_name = world_package_name or "unknown"
494
445
 
495
446
  # Build local agent images if agents_dir is provided
447
+ built_agents: list[str] = []
496
448
  if agents_dir:
497
449
  agents_dir = agents_dir.expanduser().resolve()
498
- agent_images = _extract_agent_images_from_config(config_data)
450
+ # Extract all agent names (including from full registry paths) when agents_dir is provided
451
+ agent_images = _extract_agent_images_from_config(config_data, extract_all=True)
499
452
  if agent_images:
500
- console.print(f"[blue]Building agent images: {agent_images}[/blue]")
453
+ console.print(f"[blue]Checking for local agent builds: {agent_images}[/blue]")
501
454
  for agent_name in agent_images:
502
- success = _build_agent_image(agent_name, agents_dir, platform_override)
503
- if not success:
504
- raise RuntimeError(f"Failed to build agent image: {agent_name}")
455
+ # Check if agent exists locally
456
+ agent_path = agents_dir / agent_name
457
+ if agent_path.exists() and (agent_path / "Dockerfile").exists():
458
+ console.print(f"[blue]Building local agent: {agent_name}[/blue]")
459
+ success = _build_agent_image(agent_name, agents_dir, platform_override)
460
+ if not success:
461
+ raise RuntimeError(f"Failed to build agent image: {agent_name}")
462
+ built_agents.append(agent_name)
463
+ else:
464
+ console.print(f"[dim]No local agent found for '{agent_name}', using remote image[/dim]")
465
+
466
+ # Update config to use local images for built agents
467
+ if built_agents:
468
+ config_data = _update_config_with_local_images(config_data, built_agents)
469
+ console.print(f"[green]✅ Using local builds for: {built_agents}[/green]")
505
470
 
506
471
  # Import world module to get config class for environment detection
507
472
  # We need to dynamically load the world from world_dir