plato-sdk-v2 2.5.1__py3-none-any.whl → 2.6.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.
Files changed (52) hide show
  1. plato/_generated/__init__.py +1 -1
  2. plato/_generated/api/v1/evals/get_scores_by_user.py +7 -0
  3. plato/_generated/api/v1/testcases/get_testcases.py +14 -0
  4. plato/_generated/api/v2/sessions/setup_sandbox.py +2 -2
  5. plato/_generated/models/__init__.py +42 -2
  6. plato/agents/runner.py +2 -33
  7. plato/chronos/api/admin/__init__.py +17 -0
  8. plato/chronos/api/admin/clear_database_api_admin_clear_db_post.py +57 -0
  9. plato/chronos/api/admin/sync_agents_api_admin_sync_agents_post.py +67 -0
  10. plato/chronos/api/admin/sync_all_api_admin_sync_all_post.py +67 -0
  11. plato/chronos/api/admin/sync_runtimes_api_admin_sync_runtimes_post.py +67 -0
  12. plato/chronos/api/admin/sync_worlds_api_admin_sync_worlds_post.py +67 -0
  13. plato/chronos/api/agents/list_agents.py +5 -5
  14. plato/chronos/api/checkpoints/__init__.py +8 -0
  15. plato/chronos/api/checkpoints/list_checkpoints.py +52 -0
  16. plato/chronos/api/checkpoints/preview_checkpoint.py +74 -0
  17. plato/chronos/api/events/__init__.py +8 -0
  18. plato/chronos/api/events/get_session_event.py +68 -0
  19. plato/chronos/api/events/list_session_events.py +62 -0
  20. plato/chronos/api/jobs/launch_job.py +8 -2
  21. plato/chronos/api/otel/__init__.py +8 -0
  22. plato/chronos/api/otel/get_session_traces_api_otel_sessions__session_id__traces_get.py +56 -0
  23. plato/chronos/api/otel/receive_traces_api_otel_v1_traces_post.py +49 -0
  24. plato/chronos/api/registry/list_registry_agents_api_registry_agents_get.py +5 -5
  25. plato/chronos/api/runtimes/__init__.py +2 -1
  26. plato/chronos/api/runtimes/get_runtime_logs.py +61 -0
  27. plato/chronos/api/sessions/__init__.py +24 -1
  28. plato/chronos/api/sessions/close_session.py +66 -0
  29. plato/chronos/api/sessions/complete_session.py +74 -0
  30. plato/chronos/api/sessions/create_session.py +69 -0
  31. plato/chronos/api/sessions/get_session.py +20 -2
  32. plato/chronos/api/sessions/get_session_bash_logs_download.py +61 -0
  33. plato/chronos/api/sessions/get_session_envs.py +62 -0
  34. plato/chronos/api/sessions/get_session_live_logs.py +62 -0
  35. plato/chronos/api/sessions/get_session_logs.py +3 -3
  36. plato/chronos/api/sessions/get_session_status.py +62 -0
  37. plato/chronos/api/sessions/list_sessions.py +20 -2
  38. plato/chronos/api/sessions/list_tags.py +80 -0
  39. plato/chronos/api/sessions/update_session_tags.py +68 -0
  40. plato/chronos/models/__init__.py +241 -196
  41. plato/v1/cli/chronos.py +66 -4
  42. plato/v2/__init__.py +8 -0
  43. plato/v2/async_/__init__.py +4 -0
  44. plato/v2/async_/chronos.py +419 -0
  45. plato/v2/sync/__init__.py +3 -0
  46. plato/v2/sync/chronos.py +419 -0
  47. plato/worlds/base.py +3 -3
  48. plato/worlds/config.py +3 -7
  49. {plato_sdk_v2-2.5.1.dist-info → plato_sdk_v2-2.6.1.dist-info}/METADATA +1 -1
  50. {plato_sdk_v2-2.5.1.dist-info → plato_sdk_v2-2.6.1.dist-info}/RECORD +52 -25
  51. {plato_sdk_v2-2.5.1.dist-info → plato_sdk_v2-2.6.1.dist-info}/WHEEL +0 -0
  52. {plato_sdk_v2-2.5.1.dist-info → plato_sdk_v2-2.6.1.dist-info}/entry_points.txt +0 -0
plato/v1/cli/chronos.py CHANGED
@@ -98,9 +98,13 @@ def launch(
98
98
  raise typer.Exit(1)
99
99
 
100
100
  # Build request
101
+ # Normalize tags for ltree: replace '-' with '_', ':' with '.'
102
+ raw_tags = job_config.get("tags", [])
103
+ normalized_tags = [tag.replace("-", "_").replace(":", ".").replace(" ", "_") for tag in raw_tags]
101
104
  request_body = {
102
105
  "world": job_config["world"],
103
106
  "runtime": job_config.get("runtime", {}),
107
+ "tags": normalized_tags,
104
108
  }
105
109
 
106
110
  world_package = job_config["world"]["package"]
@@ -371,6 +375,7 @@ async def _create_chronos_session(
371
375
  world_name: str,
372
376
  world_config: dict,
373
377
  plato_session_id: str | None = None,
378
+ tags: list[str] | None = None,
374
379
  ) -> dict:
375
380
  """Create a session in Chronos."""
376
381
  import httpx
@@ -384,6 +389,7 @@ async def _create_chronos_session(
384
389
  "world_name": world_name,
385
390
  "world_config": world_config,
386
391
  "plato_session_id": plato_session_id,
392
+ "tags": tags or [],
387
393
  },
388
394
  headers={"x-api-key": api_key},
389
395
  )
@@ -541,12 +547,14 @@ async def _run_dev_impl(
541
547
 
542
548
  # Create Chronos session
543
549
  console.print("[blue]Creating Chronos session...[/blue]")
550
+ tags = raw_config.get("tags", [])
544
551
  chronos_session = await _create_chronos_session(
545
552
  chronos_url=chronos_url,
546
553
  api_key=api_key,
547
554
  world_name=world_name,
548
555
  world_config=config_data,
549
556
  plato_session_id=plato_session_id,
557
+ tags=tags,
550
558
  )
551
559
  chronos_session_id = chronos_session["public_id"]
552
560
  console.print(f"[green]✅ Created Chronos session: {chronos_session_id}[/green]")
@@ -634,10 +642,6 @@ async def _run_dev_impl(
634
642
  f"UPLOAD_URL={chronos_session.get('upload_url', '')}",
635
643
  ]
636
644
 
637
- # Add secrets as env vars
638
- for key, value in config_data.get("secrets", {}).items():
639
- docker_cmd.extend(["-e", f"{key.upper()}={value}"])
640
-
641
645
  # Use world runner image
642
646
  docker_cmd.append(world_runner_image)
643
647
 
@@ -699,6 +703,64 @@ async def _run_dev_impl(
699
703
  await plato.close()
700
704
 
701
705
 
706
+ @chronos_app.command()
707
+ def stop(
708
+ session_id: Annotated[
709
+ str,
710
+ typer.Argument(help="Session ID to stop"),
711
+ ],
712
+ chronos_url: str = typer.Option(
713
+ None,
714
+ "--url",
715
+ "-u",
716
+ envvar="CHRONOS_URL",
717
+ help="Chronos API URL (default: https://chronos.plato.so)",
718
+ ),
719
+ api_key: str = typer.Option(
720
+ None,
721
+ "--api-key",
722
+ "-k",
723
+ envvar="PLATO_API_KEY",
724
+ help="Plato API key for authentication",
725
+ ),
726
+ ):
727
+ """
728
+ Stop a running Chronos session.
729
+
730
+ This marks the session as cancelled with status reason "User cancelled".
731
+
732
+ Examples:
733
+ plato chronos stop abc123
734
+ plato chronos stop abc123 --url https://chronos.plato.so
735
+ """
736
+ # Set defaults
737
+ if not chronos_url:
738
+ chronos_url = "https://chronos.plato.so"
739
+
740
+ if not api_key:
741
+ console.print("[red]❌ No API key provided[/red]")
742
+ console.print("Set PLATO_API_KEY environment variable or use --api-key")
743
+ raise typer.Exit(1)
744
+
745
+ console.print(f"[yellow]⏹ Stopping session {session_id}...[/yellow]")
746
+
747
+ async def _stop():
748
+ await _complete_chronos_session(
749
+ chronos_url=chronos_url,
750
+ api_key=api_key,
751
+ session_id=session_id,
752
+ status="cancelled",
753
+ error_message="User cancelled",
754
+ )
755
+
756
+ try:
757
+ asyncio.run(_stop())
758
+ console.print(f"[green]✅ Session {session_id} stopped[/green]")
759
+ except Exception as e:
760
+ console.print(f"[red]❌ Failed to stop session: {e}[/red]")
761
+ raise typer.Exit(1)
762
+
763
+
702
764
  @chronos_app.command()
703
765
  def dev(
704
766
  config: Annotated[
plato/v2/__init__.py CHANGED
@@ -3,6 +3,7 @@
3
3
  # Usage:
4
4
  # from plato.v2 import Plato, Session, Environment # Sync
5
5
  # from plato.v2 import AsyncPlato, AsyncSession, AsyncEnvironment # Async
6
+ # from plato.v2 import Chronos, AsyncChronos # Chronos job management
6
7
  # from plato.v2 import Env, SimConfigCompute, Flow # Helpers
7
8
 
8
9
  # Models
@@ -11,12 +12,15 @@ from plato._generated.models import ArtifactInfoResponse, Flow
11
12
  from plato.v2 import async_, sync
12
13
 
13
14
  # Async exports (prefixed with Async)
15
+ from plato.v2.async_.chronos import AsyncChronos
16
+ from plato.v2.async_.chronos import ChronosSession as AsyncChronosSession
14
17
  from plato.v2.async_.client import AsyncPlato
15
18
  from plato.v2.async_.environment import Environment as AsyncEnvironment
16
19
  from plato.v2.async_.flow_executor import FlowExecutionError as AsyncFlowExecutionError
17
20
  from plato.v2.async_.flow_executor import FlowExecutor as AsyncFlowExecutor
18
21
  from plato.v2.async_.session import SerializedSession
19
22
  from plato.v2.async_.session import Session as AsyncSession
23
+ from plato.v2.sync.chronos import Chronos, ChronosSession
20
24
  from plato.v2.sync.client import Plato
21
25
  from plato.v2.sync.environment import Environment
22
26
  from plato.v2.sync.flow_executor import FlowExecutionError, FlowExecutor
@@ -40,6 +44,8 @@ __all__ = [
40
44
  "FlowExecutor",
41
45
  "FlowExecutionError",
42
46
  "ArtifactInfoResponse",
47
+ "Chronos",
48
+ "ChronosSession",
43
49
  # Async
44
50
  "AsyncPlato",
45
51
  "AsyncSession",
@@ -47,6 +53,8 @@ __all__ = [
47
53
  "AsyncFlowExecutor",
48
54
  "AsyncFlowExecutionError",
49
55
  "SerializedSession",
56
+ "AsyncChronos",
57
+ "AsyncChronosSession",
50
58
  # Models
51
59
  "Flow",
52
60
  # Helpers
@@ -1,6 +1,8 @@
1
1
  """Plato SDK v2 - Async API."""
2
2
 
3
3
  from plato._generated.models import ArtifactInfoResponse
4
+ from plato.v2.async_.chronos import AsyncChronos
5
+ from plato.v2.async_.chronos import ChronosSession as AsyncChronosSession
4
6
  from plato.v2.async_.client import AsyncPlato as Plato
5
7
  from plato.v2.async_.environment import Environment
6
8
  from plato.v2.async_.flow_executor import FlowExecutionError, FlowExecutor
@@ -14,4 +16,6 @@ __all__ = [
14
16
  "LoginResult",
15
17
  "FlowExecutor",
16
18
  "FlowExecutionError",
19
+ "AsyncChronos",
20
+ "AsyncChronosSession",
17
21
  ]
@@ -0,0 +1,419 @@
1
+ """Plato SDK v2 - Asynchronous Chronos Client.
2
+
3
+ Provides high-level APIs for managing Chronos sessions and jobs programmatically.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ import logging
10
+ import os
11
+ from typing import Any
12
+
13
+ import httpx
14
+ from dotenv import load_dotenv
15
+
16
+ from plato.chronos.api.jobs import launch_job
17
+ from plato.chronos.api.sessions import (
18
+ close_session,
19
+ complete_session,
20
+ get_session,
21
+ get_session_envs,
22
+ get_session_logs,
23
+ get_session_status,
24
+ list_sessions,
25
+ list_tags,
26
+ update_session_tags,
27
+ )
28
+ from plato.chronos.models import (
29
+ CompleteSessionRequest,
30
+ LaunchJobRequest,
31
+ LaunchJobResponse,
32
+ RuntimeConfig,
33
+ SessionEnvsResponse,
34
+ SessionListResponse,
35
+ SessionLogsResponse,
36
+ SessionResponse,
37
+ SessionStatusResponse,
38
+ TagsListResponse,
39
+ UpdateTagsRequest,
40
+ WorldConfig,
41
+ )
42
+
43
+ load_dotenv()
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+ DEFAULT_CHRONOS_URL = "https://chronos.plato.so"
48
+ DEFAULT_TIMEOUT = 120.0
49
+
50
+
51
+ class ChronosSession:
52
+ """Wrapper for a Chronos session with convenient methods.
53
+
54
+ Provides methods to check status, get logs, update tags, and stop the session.
55
+ """
56
+
57
+ def __init__(
58
+ self,
59
+ http_client: httpx.AsyncClient,
60
+ api_key: str,
61
+ session_id: str,
62
+ plato_session_id: str | None = None,
63
+ ):
64
+ self._http = http_client
65
+ self._api_key = api_key
66
+ self._session_id = session_id
67
+ self._plato_session_id = plato_session_id
68
+
69
+ @property
70
+ def session_id(self) -> str:
71
+ """Get the Chronos session public ID."""
72
+ return self._session_id
73
+
74
+ @property
75
+ def plato_session_id(self) -> str | None:
76
+ """Get the underlying Plato session ID."""
77
+ return self._plato_session_id
78
+
79
+ async def get_status(self) -> SessionStatusResponse:
80
+ """Get the current status of the session (lightweight).
81
+
82
+ Returns:
83
+ SessionStatusResponse with status and status_reason.
84
+ """
85
+ return await get_session_status.asyncio(
86
+ client=self._http,
87
+ public_id=self._session_id,
88
+ x_api_key=self._api_key,
89
+ )
90
+
91
+ async def get_details(self) -> SessionResponse:
92
+ """Get full session details including trajectory and world config.
93
+
94
+ Returns:
95
+ SessionResponse with full session details.
96
+ """
97
+ return await get_session.asyncio(
98
+ client=self._http,
99
+ public_id=self._session_id,
100
+ x_api_key=self._api_key,
101
+ )
102
+
103
+ async def get_envs(self) -> SessionEnvsResponse:
104
+ """Get environment information for this session.
105
+
106
+ Returns:
107
+ SessionEnvsResponse with list of environments.
108
+ """
109
+ return await get_session_envs.asyncio(
110
+ client=self._http,
111
+ public_id=self._session_id,
112
+ x_api_key=self._api_key,
113
+ )
114
+
115
+ async def get_logs(
116
+ self,
117
+ limit: int = 10000,
118
+ ) -> SessionLogsResponse:
119
+ """Get logs for this session.
120
+
121
+ Args:
122
+ limit: Maximum number of log entries to return.
123
+
124
+ Returns:
125
+ SessionLogsResponse with log entries.
126
+ """
127
+ return await get_session_logs.asyncio(
128
+ client=self._http,
129
+ public_id=self._session_id,
130
+ limit=limit,
131
+ x_api_key=self._api_key,
132
+ )
133
+
134
+ async def update_tags(self, tags: list[str]) -> SessionResponse:
135
+ """Update tags for this session.
136
+
137
+ Args:
138
+ tags: List of tags (use '.' for hierarchy, '_' instead of '-').
139
+
140
+ Returns:
141
+ Updated SessionResponse.
142
+ """
143
+ # Normalize tags
144
+ normalized = [tag.replace("-", "_").replace(":", ".").replace(" ", "_") for tag in tags]
145
+ request = UpdateTagsRequest(tags=normalized)
146
+ return await update_session_tags.asyncio(
147
+ client=self._http,
148
+ public_id=self._session_id,
149
+ body=request,
150
+ x_api_key=self._api_key,
151
+ )
152
+
153
+ async def stop(self, error_message: str = "Cancelled by user") -> SessionResponse:
154
+ """Stop/cancel this session.
155
+
156
+ Args:
157
+ error_message: Reason for stopping.
158
+
159
+ Returns:
160
+ Updated SessionResponse.
161
+ """
162
+ request = CompleteSessionRequest(
163
+ status="cancelled",
164
+ error_message=error_message,
165
+ )
166
+ return await complete_session.asyncio(
167
+ client=self._http,
168
+ public_id=self._session_id,
169
+ body=request,
170
+ x_api_key=self._api_key,
171
+ )
172
+
173
+ async def complete(
174
+ self,
175
+ status: str = "completed",
176
+ exit_code: int | None = None,
177
+ error_message: str | None = None,
178
+ ) -> SessionResponse:
179
+ """Mark session as completed or failed.
180
+
181
+ Args:
182
+ status: Final status ('completed', 'failed', or 'cancelled').
183
+ exit_code: Optional exit code from world runner.
184
+ error_message: Error message if failed.
185
+
186
+ Returns:
187
+ Updated SessionResponse.
188
+ """
189
+ request = CompleteSessionRequest(
190
+ status=status,
191
+ exit_code=exit_code,
192
+ error_message=error_message,
193
+ )
194
+ return await complete_session.asyncio(
195
+ client=self._http,
196
+ public_id=self._session_id,
197
+ body=request,
198
+ x_api_key=self._api_key,
199
+ )
200
+
201
+ async def close(self) -> None:
202
+ """Close the Plato session (release VM resources).
203
+
204
+ Note: This closes the underlying Plato session, not just the Chronos record.
205
+ """
206
+ await close_session.asyncio(
207
+ client=self._http,
208
+ public_id=self._session_id,
209
+ x_api_key=self._api_key,
210
+ )
211
+
212
+ async def wait_until_complete(
213
+ self,
214
+ timeout: float = 3600.0,
215
+ poll_interval: float = 5.0,
216
+ ) -> SessionResponse:
217
+ """Wait until the session reaches a terminal state.
218
+
219
+ Args:
220
+ timeout: Maximum time to wait in seconds.
221
+ poll_interval: Time between status checks.
222
+
223
+ Returns:
224
+ Final SessionResponse.
225
+
226
+ Raises:
227
+ TimeoutError: If session doesn't complete within timeout.
228
+ """
229
+ terminal_statuses = {"completed", "failed", "cancelled", "error"}
230
+ elapsed = 0.0
231
+
232
+ while elapsed < timeout:
233
+ status_response = await self.get_status()
234
+ if status_response.status in terminal_statuses:
235
+ return await self.get_details()
236
+
237
+ await asyncio.sleep(poll_interval)
238
+ elapsed += poll_interval
239
+
240
+ raise TimeoutError(f"Session {self._session_id} did not complete within {timeout} seconds")
241
+
242
+ def __repr__(self) -> str:
243
+ return f"ChronosSession(session_id={self._session_id!r})"
244
+
245
+
246
+ class AsyncChronos:
247
+ """Asynchronous client for Chronos job management API.
248
+
249
+ Provides high-level methods for launching jobs, managing sessions,
250
+ and monitoring job status.
251
+
252
+ Usage:
253
+ from plato.v2.async_ import AsyncChronos
254
+
255
+ async with AsyncChronos() as chronos:
256
+ # Launch a job
257
+ session = await chronos.launch(
258
+ world_package="plato-world-computer-use",
259
+ world_config={"task": "Navigate to google.com"},
260
+ tags=["test", "project.my_project"],
261
+ )
262
+
263
+ # Wait for completion
264
+ result = await session.wait_until_complete()
265
+ print(f"Status: {result.status}")
266
+
267
+ # Or poll manually
268
+ while True:
269
+ status = await session.get_status()
270
+ if status.status in ("completed", "failed"):
271
+ break
272
+ await asyncio.sleep(5)
273
+ """
274
+
275
+ def __init__(
276
+ self,
277
+ api_key: str | None = None,
278
+ base_url: str | None = None,
279
+ timeout: float = DEFAULT_TIMEOUT,
280
+ ):
281
+ """Initialize the Chronos client.
282
+
283
+ Args:
284
+ api_key: Plato API key. Falls back to PLATO_API_KEY env var.
285
+ base_url: Chronos API base URL. Falls back to CHRONOS_URL env var.
286
+ timeout: Request timeout in seconds.
287
+ """
288
+ resolved_api_key = api_key or os.environ.get("PLATO_API_KEY")
289
+ if not resolved_api_key:
290
+ raise ValueError("API key required. Set PLATO_API_KEY or pass api_key=")
291
+ self._api_key: str = resolved_api_key
292
+
293
+ url = base_url or os.environ.get("CHRONOS_URL", DEFAULT_CHRONOS_URL)
294
+ self.base_url = url.rstrip("/")
295
+ self.timeout = timeout
296
+
297
+ self._http = httpx.AsyncClient(
298
+ base_url=self.base_url,
299
+ timeout=httpx.Timeout(timeout),
300
+ )
301
+
302
+ async def launch(
303
+ self,
304
+ world_package: str,
305
+ world_config: dict[str, Any] | None = None,
306
+ runtime_artifact_id: str | None = None,
307
+ tags: list[str] | None = None,
308
+ ) -> ChronosSession:
309
+ """Launch a new Chronos job.
310
+
311
+ Args:
312
+ world_package: World package name with optional version
313
+ (e.g., "plato-world-computer-use:0.1.0").
314
+ world_config: Configuration passed to the world runner.
315
+ runtime_artifact_id: Optional runtime artifact ID for cached environment.
316
+ tags: Optional tags for organizing sessions (use '.' for hierarchy).
317
+
318
+ Returns:
319
+ ChronosSession for monitoring and managing the job.
320
+
321
+ Example:
322
+ session = await chronos.launch(
323
+ world_package="plato-world-computer-use",
324
+ world_config={
325
+ "task": "Navigate to google.com",
326
+ "agents": [{"name": "computer-use", "version": "latest"}],
327
+ },
328
+ tags=["project.my_project", "env.dev"],
329
+ )
330
+ """
331
+ # Normalize tags
332
+ normalized_tags = None
333
+ if tags:
334
+ normalized_tags = [tag.replace("-", "_").replace(":", ".").replace(" ", "_") for tag in tags]
335
+
336
+ request = LaunchJobRequest(
337
+ world=WorldConfig(
338
+ package=world_package,
339
+ config=world_config,
340
+ ),
341
+ runtime=RuntimeConfig(artifact_id=runtime_artifact_id) if runtime_artifact_id else None,
342
+ tags=normalized_tags,
343
+ )
344
+
345
+ response: LaunchJobResponse = await launch_job.asyncio(
346
+ client=self._http,
347
+ body=request,
348
+ x_api_key=self._api_key,
349
+ )
350
+
351
+ logger.info(f"Launched Chronos job: {response.session_id}")
352
+
353
+ return ChronosSession(
354
+ http_client=self._http,
355
+ api_key=self.api_key,
356
+ session_id=response.session_id,
357
+ plato_session_id=response.plato_session_id,
358
+ )
359
+
360
+ async def get_session(self, session_id: str) -> ChronosSession:
361
+ """Get a ChronosSession wrapper for an existing session.
362
+
363
+ Args:
364
+ session_id: The Chronos session public ID.
365
+
366
+ Returns:
367
+ ChronosSession for the specified session.
368
+ """
369
+ # Verify the session exists by fetching status
370
+ await get_session_status.asyncio(
371
+ client=self._http,
372
+ public_id=session_id,
373
+ x_api_key=self._api_key,
374
+ )
375
+
376
+ return ChronosSession(
377
+ http_client=self._http,
378
+ api_key=self.api_key,
379
+ session_id=session_id,
380
+ )
381
+
382
+ async def list_sessions(
383
+ self,
384
+ tag: str | None = None,
385
+ ) -> SessionListResponse:
386
+ """List Chronos sessions.
387
+
388
+ Args:
389
+ tag: Filter by tag (fuzzy substring match).
390
+
391
+ Returns:
392
+ SessionListResponse with list of sessions.
393
+ """
394
+ return await list_sessions.asyncio(
395
+ client=self._http,
396
+ tag=tag,
397
+ x_api_key=self._api_key,
398
+ )
399
+
400
+ async def list_tags(self) -> TagsListResponse:
401
+ """List all unique tags across sessions.
402
+
403
+ Returns:
404
+ TagsListResponse with list of tags.
405
+ """
406
+ return await list_tags.asyncio(
407
+ client=self._http,
408
+ x_api_key=self._api_key,
409
+ )
410
+
411
+ async def close(self) -> None:
412
+ """Close the underlying HTTP client."""
413
+ await self._http.aclose()
414
+
415
+ async def __aenter__(self) -> AsyncChronos:
416
+ return self
417
+
418
+ async def __aexit__(self, *args: Any) -> None:
419
+ await self.close()
plato/v2/sync/__init__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Plato SDK v2 - Sync API."""
2
2
 
3
3
  from plato._generated.models import ArtifactInfoResponse
4
+ from plato.v2.sync.chronos import Chronos, ChronosSession
4
5
  from plato.v2.sync.client import Plato
5
6
  from plato.v2.sync.environment import Environment
6
7
  from plato.v2.sync.session import Session
@@ -10,4 +11,6 @@ __all__ = [
10
11
  "Plato",
11
12
  "Session",
12
13
  "Environment",
14
+ "Chronos",
15
+ "ChronosSession",
13
16
  ]