plato-sdk-v2 2.8.1__py3-none-any.whl → 2.8.2__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/pm.py CHANGED
@@ -22,12 +22,10 @@ from plato._generated.api.v1.simulator import (
22
22
  update_simulator_status,
23
23
  update_tag,
24
24
  )
25
- from plato._generated.api.v2.sessions import state as sessions_state
26
25
  from plato._generated.models import (
27
26
  AddReviewRequest,
28
27
  AppApiV1SimulatorRoutesUpdateSimulatorRequest,
29
28
  Authentication,
30
- Flow,
31
29
  Outcome,
32
30
  ReviewType,
33
31
  UpdateStatusRequest,
@@ -42,9 +40,9 @@ from plato.cli.utils import (
42
40
  require_sandbox_field,
43
41
  require_sandbox_state,
44
42
  )
45
- from plato.v2.async_.client import AsyncPlato
46
- from plato.v2.async_.flow_executor import FlowExecutor
47
- from plato.v2.types import Env
43
+ from plato.v1.flow_executor import FlowExecutor
44
+ from plato.v1.models.flow import Flow
45
+ from plato.v1.sdk import Plato
48
46
 
49
47
  # =============================================================================
50
48
  # CONSTANTS
@@ -318,25 +316,30 @@ def review_base(
318
316
  )
319
317
 
320
318
  async def _review_base():
319
+ import warnings
320
+
321
321
  base_url = _get_base_url()
322
- plato = AsyncPlato(api_key=api_key, base_url=base_url)
323
- session = None
322
+ # v1 SDK expects base_url to include /api suffix
323
+ v1_base_url = f"{base_url}/api"
324
+ # Suppress the deprecation warning from v1 Plato
325
+ with warnings.catch_warnings():
326
+ warnings.simplefilter("ignore", DeprecationWarning)
327
+ plato = Plato(api_key=api_key, base_url=v1_base_url)
328
+ env = None
324
329
  playwright = None
325
330
  browser = None
326
- login_result = None
327
331
 
328
332
  try:
329
- http_client = plato._http
330
-
331
333
  # simulator_name is guaranteed set by parse_simulator_artifact (or we exit)
332
334
  assert simulator_name is not None, "simulator_name must be set"
333
335
 
334
- # Get simulator by name
335
- sim = await get_simulator_by_name.asyncio(
336
- client=http_client,
337
- name=simulator_name,
338
- x_api_key=api_key,
339
- )
336
+ # Get simulator by name using httpx for API calls
337
+ async with httpx.AsyncClient(base_url=base_url, timeout=60.0) as http_client:
338
+ sim = await get_simulator_by_name.asyncio(
339
+ client=http_client,
340
+ name=simulator_name,
341
+ x_api_key=api_key,
342
+ )
340
343
  simulator_id = sim.id
341
344
  current_config = sim.config or {}
342
345
  current_status = current_config.get("status", "not_started")
@@ -357,26 +360,27 @@ def review_base(
357
360
 
358
361
  console.print(f"[cyan]Using artifact:[/cyan] {artifact_id}")
359
362
 
360
- # Try to create session with environment from artifact
363
+ # Try to create environment from artifact using v1 API
361
364
  try:
362
365
  console.print(f"[cyan]Creating {simulator_name} environment with artifact {artifact_id}...[/cyan]")
363
- session = await plato.sessions.create(
364
- envs=[Env.artifact(artifact_id)],
365
- timeout=300,
366
+ env = await plato.make_environment(
367
+ env_id=simulator_name,
368
+ artifact_id=artifact_id,
366
369
  )
367
- console.print(f"[green]✅ Session created: {session.session_id}[/green]")
370
+ console.print(f"[green]✅ Environment created: {env.id}[/green]")
371
+
372
+ # Wait for environment to be ready
373
+ console.print("[cyan]Waiting for environment to be ready...[/cyan]")
374
+ await env.wait_for_ready(timeout=300)
375
+ console.print("[green]✅ Environment ready![/green]")
368
376
 
369
377
  # Reset
370
378
  console.print("[cyan]Resetting environment...[/cyan]")
371
- await session.reset()
379
+ await env.reset()
372
380
  console.print("[green]✅ Environment reset complete![/green]")
373
381
 
374
- # Get public URL
375
- public_urls = await session.get_public_url()
376
- first_alias = session.envs[0].alias if session.envs else None
377
- public_url = public_urls.get(first_alias) if first_alias else None
378
- if not public_url and public_urls:
379
- public_url = list(public_urls.values())[0]
382
+ # Get public URL (v1 returns string directly)
383
+ public_url = await env.get_public_url()
380
384
  console.print(f"[cyan]Public URL:[/cyan] {public_url}")
381
385
 
382
386
  # Launch Playwright browser and login
@@ -452,70 +456,66 @@ def review_base(
452
456
  except Exception as e:
453
457
  console.print(f"[yellow]⚠️ Flow execution error: {e}[/yellow]")
454
458
  else:
455
- # Use default login via session.login()
459
+ # Use default login via env.login() (v1 API takes Page, not Browser)
456
460
  if fake_time:
457
461
  console.print("[yellow]⚠️ --clock with default login may not work correctly.[/yellow]")
458
462
  console.print("[yellow] Use --local with a flow file for reliable clock testing.[/yellow]")
459
463
  try:
460
- login_result = await session.login(browser, dataset="base")
461
- page = list(login_result.pages.values())[0] if login_result.pages else None
462
- console.print("[green]✅ Logged into environment[/green]")
463
- except Exception as e:
464
- console.print(f"[yellow]⚠️ Login error: {e}[/yellow]")
464
+ # Create page and navigate to public URL first
465
465
  page = await browser.new_page()
466
- # Install fake clock on fallback page
467
466
  if fake_time:
468
467
  await page.clock.install(time=fake_time)
469
468
  console.print(f"[green]✅ Fake clock installed: {fake_time.isoformat()}[/green]")
470
469
  if public_url:
471
470
  await page.goto(public_url)
471
+ # v1 login takes a Page and uses from_api=True to fetch flows from server
472
+ await env.login(page, dataset="base", from_api=True)
473
+ console.print("[green]✅ Logged into environment[/green]")
474
+ except Exception as e:
475
+ console.print(f"[yellow]⚠️ Login error: {e}[/yellow]")
476
+ # Page already created above, just navigate if not already done
477
+ if public_url and page:
478
+ try:
479
+ await page.goto(public_url)
480
+ except Exception:
481
+ pass
472
482
 
473
483
  # ALWAYS check state after login to verify no mutations
474
484
  console.print("\n[cyan]Checking environment state after login...[/cyan]")
475
485
  has_mutations = False
476
486
  has_errors = False
477
487
  try:
478
- state_response = await sessions_state.asyncio(
479
- client=http_client,
480
- session_id=session.session_id,
481
- merge_mutations=True,
482
- x_api_key=api_key,
483
- )
484
- if state_response and state_response.results:
485
- for jid, result in state_response.results.items():
486
- state_data = result.state if hasattr(result, "state") else result
487
- console.print(f"\n[bold cyan]Job {jid}:[/bold cyan]")
488
-
489
- if isinstance(state_data, dict):
490
- # Check for error in state response
491
- if "error" in state_data:
492
- has_errors = True
493
- console.print("\n[bold red]❌ State API Error:[/bold red]")
494
- console.print(f"[red]{state_data['error']}[/red]")
495
- continue
496
-
497
- mutations = state_data.pop("mutations", [])
498
- console.print("\n[bold]State:[/bold]")
499
- console.print(json.dumps(state_data, indent=2, default=str))
500
- if mutations:
501
- has_mutations = True
502
- console.print(f"\n[bold red]Mutations ({len(mutations)}):[/bold red]")
503
- console.print(json.dumps(mutations, indent=2, default=str))
504
- else:
505
- console.print("\n[green]No mutations recorded[/green]")
506
- else:
507
- console.print(f"[yellow]Unexpected state format: {type(state_data)}[/yellow]")
508
-
509
- if has_errors:
510
- console.print("\n[bold red]❌ State check failed due to errors![/bold red]")
511
- console.print("[yellow]The worker may not be properly connected.[/yellow]")
512
- elif has_mutations:
513
- console.print("\n[bold red]⚠️ WARNING: Login flow created mutations![/bold red]")
514
- console.print("[yellow]The login flow should NOT modify database state.[/yellow]")
488
+ # v1 API: env.get_state() returns state dict directly
489
+ state_data = await env.get_state(merge_mutations=True)
490
+ console.print(f"\n[bold cyan]Environment {env.id}:[/bold cyan]")
491
+
492
+ if isinstance(state_data, dict):
493
+ # Check for error in state response (only if error has a truthy value)
494
+ if state_data.get("error"):
495
+ has_errors = True
496
+ console.print("\n[bold red]❌ State API Error:[/bold red]")
497
+ console.print(f"[red]{state_data['error']}[/red]")
515
498
  else:
516
- console.print("\n[bold green]✅ Login flow verified - no mutations created[/bold green]")
499
+ mutations = state_data.pop("mutations", [])
500
+ console.print("\n[bold]State:[/bold]")
501
+ console.print(json.dumps(state_data, indent=2, default=str))
502
+ if mutations:
503
+ has_mutations = True
504
+ console.print(f"\n[bold red]Mutations ({len(mutations)}):[/bold red]")
505
+ console.print(json.dumps(mutations, indent=2, default=str))
506
+ else:
507
+ console.print("\n[green]No mutations recorded[/green]")
517
508
  else:
518
- console.print("[yellow]No state data available[/yellow]")
509
+ console.print(f"[yellow]Unexpected state format: {type(state_data)}[/yellow]")
510
+
511
+ if has_errors:
512
+ console.print("\n[bold red]❌ State check failed due to errors![/bold red]")
513
+ console.print("[yellow]The worker may not be properly connected.[/yellow]")
514
+ elif has_mutations:
515
+ console.print("\n[bold red]⚠️ WARNING: Login flow created mutations![/bold red]")
516
+ console.print("[yellow]The login flow should NOT modify database state.[/yellow]")
517
+ else:
518
+ console.print("\n[bold green]✅ Login flow verified - no mutations created[/bold green]")
519
519
  except Exception as e:
520
520
  console.print(f"[red]❌ Error getting state: {e}[/red]")
521
521
 
@@ -562,37 +562,26 @@ def review_base(
562
562
  elif command in ["state", "s"]:
563
563
  console.print("\n[cyan]Getting environment state with mutations...[/cyan]")
564
564
  try:
565
- # Call API directly with merge_mutations=True to include mutations
566
- state_response = await sessions_state.asyncio(
567
- client=http_client,
568
- session_id=session.session_id,
569
- merge_mutations=True,
570
- x_api_key=api_key,
571
- )
572
- if state_response and state_response.results:
573
- for jid, result in state_response.results.items():
574
- state_data = result.state if hasattr(result, "state") else result
575
- console.print(f"\n[bold cyan]Job {jid}:[/bold cyan]")
576
-
577
- if isinstance(state_data, dict):
578
- # Check for error in state response
579
- if "error" in state_data:
580
- console.print("\n[bold red]❌ State API Error:[/bold red]")
581
- console.print(f"[red]{state_data['error']}[/red]")
582
- continue
583
-
584
- mutations = state_data.pop("mutations", [])
585
- console.print("\n[bold]State:[/bold]")
586
- console.print(json.dumps(state_data, indent=2, default=str))
587
- if mutations:
588
- console.print(f"\n[bold]Mutations ({len(mutations)}):[/bold]")
589
- console.print(json.dumps(mutations, indent=2, default=str))
590
- else:
591
- console.print("\n[yellow]No mutations recorded[/yellow]")
565
+ # v1 API: env.get_state() returns state dict directly
566
+ state_data = await env.get_state(merge_mutations=True)
567
+ console.print(f"\n[bold cyan]Environment {env.id}:[/bold cyan]")
568
+
569
+ if isinstance(state_data, dict):
570
+ # Check for error in state response (only if error has a truthy value)
571
+ if state_data.get("error"):
572
+ console.print("\n[bold red]❌ State API Error:[/bold red]")
573
+ console.print(f"[red]{state_data['error']}[/red]")
574
+ else:
575
+ mutations = state_data.pop("mutations", [])
576
+ console.print("\n[bold]State:[/bold]")
577
+ console.print(json.dumps(state_data, indent=2, default=str))
578
+ if mutations:
579
+ console.print(f"\n[bold]Mutations ({len(mutations)}):[/bold]")
580
+ console.print(json.dumps(mutations, indent=2, default=str))
592
581
  else:
593
- console.print(json.dumps(state_data, indent=2, default=str))
582
+ console.print("\n[yellow]No mutations recorded[/yellow]")
594
583
  else:
595
- console.print("[yellow]No state data available[/yellow]")
584
+ console.print(json.dumps(state_data, indent=2, default=str))
596
585
  console.print()
597
586
  except Exception as e:
598
587
  console.print(f"[red]❌ Error getting state: {e}[/red]")
@@ -633,56 +622,60 @@ def review_base(
633
622
  validate_status_transition(current_status, "env_review_requested", "review base reject")
634
623
  new_status = "env_in_progress"
635
624
 
636
- # Update status
637
- await update_simulator_status.asyncio(
638
- client=http_client,
639
- simulator_id=simulator_id,
640
- body=UpdateStatusRequest(status=new_status),
641
- x_api_key=api_key,
642
- )
643
-
644
- # Add review if rejecting
645
- if outcome == "reject":
646
- comments = ""
647
- while not comments:
648
- comments = typer.prompt("Comments (required for reject)").strip()
649
- if not comments:
650
- console.print("[yellow]Comments are required when rejecting. Please provide feedback.[/yellow]")
651
-
652
- await add_simulator_review.asyncio(
653
- client=http_client,
625
+ # Create httpx client for API calls
626
+ async with httpx.AsyncClient(base_url=base_url, timeout=60.0) as api_client:
627
+ # Update status
628
+ await update_simulator_status.asyncio(
629
+ client=api_client,
654
630
  simulator_id=simulator_id,
655
- body=AddReviewRequest(
656
- review_type=ReviewType.env,
657
- outcome=Outcome.reject,
658
- artifact_id=artifact_id,
659
- comments=comments,
660
- ),
631
+ body=UpdateStatusRequest(status=new_status),
661
632
  x_api_key=api_key,
662
633
  )
663
634
 
664
- console.print(f"[green]✅ Review submitted: {outcome}[/green]")
665
- console.print(f"[cyan]Status:[/cyan] {current_status} {new_status}")
666
-
667
- # If passed, automatically tag artifact as prod-latest
668
- if outcome == "pass" and artifact_id:
669
- console.print("\n[cyan]Tagging artifact as prod-latest...[/cyan]")
670
- try:
671
- # simulator_name and artifact_id are guaranteed to be set at this point
672
- assert simulator_name is not None
673
- await update_tag.asyncio(
674
- client=http_client,
675
- body=UpdateTagRequest(
676
- simulator_name=simulator_name,
635
+ # Add review if rejecting
636
+ if outcome == "reject":
637
+ comments = ""
638
+ while not comments:
639
+ comments = typer.prompt("Comments (required for reject)").strip()
640
+ if not comments:
641
+ console.print(
642
+ "[yellow]Comments are required when rejecting. Please provide feedback.[/yellow]"
643
+ )
644
+
645
+ await add_simulator_review.asyncio(
646
+ client=api_client,
647
+ simulator_id=simulator_id,
648
+ body=AddReviewRequest(
649
+ review_type=ReviewType.env,
650
+ outcome=Outcome.reject,
677
651
  artifact_id=artifact_id,
678
- tag_name="prod-latest",
679
- dataset="base",
652
+ comments=comments,
680
653
  ),
681
654
  x_api_key=api_key,
682
655
  )
683
- console.print(f"[green]✅ Tagged {artifact_id[:8]}... as prod-latest[/green]")
684
- except Exception as e:
685
- console.print(f"[yellow]⚠️ Could not tag as prod-latest: {e}[/yellow]")
656
+
657
+ console.print(f"[green]✅ Review submitted: {outcome}[/green]")
658
+ console.print(f"[cyan]Status:[/cyan] {current_status} {new_status}")
659
+
660
+ # If passed, automatically tag artifact as prod-latest
661
+ if outcome == "pass" and artifact_id:
662
+ console.print("\n[cyan]Tagging artifact as prod-latest...[/cyan]")
663
+ try:
664
+ # simulator_name and artifact_id are guaranteed to be set at this point
665
+ assert simulator_name is not None
666
+ await update_tag.asyncio(
667
+ client=api_client,
668
+ body=UpdateTagRequest(
669
+ simulator_name=simulator_name,
670
+ artifact_id=artifact_id,
671
+ tag_name="prod-latest",
672
+ dataset="base",
673
+ ),
674
+ x_api_key=api_key,
675
+ )
676
+ console.print(f"[green]✅ Tagged {artifact_id[:8]}... as prod-latest[/green]")
677
+ except Exception as e:
678
+ console.print(f"[yellow]⚠️ Could not tag as prod-latest: {e}[/yellow]")
686
679
 
687
680
  except typer.Exit:
688
681
  raise
@@ -693,8 +686,6 @@ def review_base(
693
686
  finally:
694
687
  # Cleanup
695
688
  try:
696
- if login_result and login_result.context:
697
- await login_result.context.close()
698
689
  if browser:
699
690
  await browser.close()
700
691
  if playwright:
@@ -702,13 +693,13 @@ def review_base(
702
693
  except Exception as e:
703
694
  console.print(f"[yellow]⚠️ Browser cleanup error: {e}[/yellow]")
704
695
 
705
- if session:
696
+ if env:
706
697
  try:
707
- console.print("[cyan]Shutting down session...[/cyan]")
708
- await session.close()
709
- console.print("[green]✅ Session shut down[/green]")
698
+ console.print("[cyan]Shutting down environment...[/cyan]")
699
+ await env.close()
700
+ console.print("[green]✅ Environment shut down[/green]")
710
701
  except Exception as e:
711
- console.print(f"[yellow]⚠️ Session cleanup error: {e}[/yellow]")
702
+ console.print(f"[yellow]⚠️ Environment cleanup error: {e}[/yellow]")
712
703
 
713
704
  try:
714
705
  await plato.close()
plato/cli/sandbox.py CHANGED
@@ -407,8 +407,9 @@ def sandbox_snapshot(
407
407
 
408
408
  Captures VM state and database for later restoration.
409
409
 
410
- Example:
411
- plato sandbox snapshot
410
+ Examples:
411
+ plato sandbox snapshot # Uses mode from state.json
412
+ plato sandbox snapshot --mode config # Override to pass local plato-config.yml and flows to artifact
412
413
  """
413
414
  with sandbox_context(working_dir, json_output, verbose) as (client, out):
414
415
  out.console.print("Creating snapshot...")