plato-sdk-v2 2.2.2__py3-none-any.whl → 2.2.3__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/agents/runner.py +26 -15
- plato/worlds/base.py +1 -0
- plato/worlds/runner.py +236 -17
- {plato_sdk_v2-2.2.2.dist-info → plato_sdk_v2-2.2.3.dist-info}/METADATA +1 -1
- {plato_sdk_v2-2.2.2.dist-info → plato_sdk_v2-2.2.3.dist-info}/RECORD +7 -7
- {plato_sdk_v2-2.2.2.dist-info → plato_sdk_v2-2.2.3.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.2.2.dist-info → plato_sdk_v2-2.2.3.dist-info}/entry_points.txt +0 -0
plato/agents/runner.py
CHANGED
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
+
import platform
|
|
9
10
|
import tempfile
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
@@ -62,21 +63,29 @@ async def run_agent(
|
|
|
62
63
|
# Build docker command
|
|
63
64
|
docker_cmd = ["docker", "run", "--rm"]
|
|
64
65
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
66
|
+
# Determine if we need host networking:
|
|
67
|
+
# - Required on Linux without iptables for connectivity
|
|
68
|
+
# - Skip on macOS where --network=host doesn't work properly
|
|
69
|
+
use_host_network = False
|
|
70
|
+
is_macos = platform.system() == "Darwin"
|
|
71
|
+
|
|
72
|
+
if not is_macos:
|
|
73
|
+
try:
|
|
74
|
+
proc = await asyncio.create_subprocess_exec(
|
|
75
|
+
"iptables",
|
|
76
|
+
"-L",
|
|
77
|
+
"-n",
|
|
78
|
+
stdout=asyncio.subprocess.DEVNULL,
|
|
79
|
+
stderr=asyncio.subprocess.DEVNULL,
|
|
80
|
+
)
|
|
81
|
+
await proc.wait()
|
|
82
|
+
has_iptables = proc.returncode == 0
|
|
83
|
+
except (FileNotFoundError, PermissionError):
|
|
84
|
+
has_iptables = False
|
|
85
|
+
|
|
86
|
+
use_host_network = not has_iptables
|
|
87
|
+
|
|
88
|
+
if use_host_network:
|
|
80
89
|
docker_cmd.extend(["--network=host", "--add-host=localhost:127.0.0.1"])
|
|
81
90
|
|
|
82
91
|
docker_cmd.extend(
|
|
@@ -96,6 +105,8 @@ async def run_agent(
|
|
|
96
105
|
docker_cmd.extend(["-e", f"{key.upper()}={value}"])
|
|
97
106
|
|
|
98
107
|
docker_cmd.append(image)
|
|
108
|
+
|
|
109
|
+
# Pass instruction via CLI arg (agents expect --instruction flag)
|
|
99
110
|
docker_cmd.extend(["--instruction", instruction])
|
|
100
111
|
|
|
101
112
|
# Run container and stream output
|
plato/worlds/base.py
CHANGED
plato/worlds/runner.py
CHANGED
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
+
import platform
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Annotated
|
|
11
12
|
|
|
@@ -135,18 +136,143 @@ def list_worlds(
|
|
|
135
136
|
typer.echo(f" {name} (v{version}): {desc}")
|
|
136
137
|
|
|
137
138
|
|
|
139
|
+
async def _build_agent_image(
|
|
140
|
+
agent_name: str,
|
|
141
|
+
agents_dir: Path,
|
|
142
|
+
plato_client_root: Path | None = None,
|
|
143
|
+
) -> bool:
|
|
144
|
+
"""Build a local agent Docker image.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
agent_name: Name of the agent (e.g., "openhands")
|
|
148
|
+
agents_dir: Directory containing agent subdirectories
|
|
149
|
+
plato_client_root: Root of plato-client repo (for dev builds), or None for prod builds
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True if build succeeded, False otherwise
|
|
153
|
+
"""
|
|
154
|
+
import subprocess
|
|
155
|
+
|
|
156
|
+
# Resolve paths to absolute
|
|
157
|
+
agents_dir = agents_dir.expanduser().resolve()
|
|
158
|
+
agent_path = agents_dir / agent_name
|
|
159
|
+
dockerfile_path = agent_path / "Dockerfile"
|
|
160
|
+
|
|
161
|
+
if not dockerfile_path.exists():
|
|
162
|
+
logger.warning(f"No Dockerfile found for agent '{agent_name}' at {dockerfile_path}")
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
image_tag = f"{agent_name}:latest"
|
|
166
|
+
|
|
167
|
+
# Determine build context and target
|
|
168
|
+
if plato_client_root:
|
|
169
|
+
plato_client_root = plato_client_root.expanduser().resolve()
|
|
170
|
+
|
|
171
|
+
if plato_client_root and plato_client_root.exists():
|
|
172
|
+
# Dev build from plato-client root (includes local python-sdk)
|
|
173
|
+
build_context = str(plato_client_root)
|
|
174
|
+
dockerfile_abs = str(dockerfile_path)
|
|
175
|
+
target = "dev"
|
|
176
|
+
logger.info(f"Building {image_tag} (dev mode from {build_context})...")
|
|
177
|
+
else:
|
|
178
|
+
# Prod build from agent directory
|
|
179
|
+
build_context = str(agent_path)
|
|
180
|
+
dockerfile_abs = str(dockerfile_path)
|
|
181
|
+
target = "prod"
|
|
182
|
+
logger.info(f"Building {image_tag} (prod mode from {build_context})...")
|
|
183
|
+
|
|
184
|
+
cmd = [
|
|
185
|
+
"docker",
|
|
186
|
+
"build",
|
|
187
|
+
"--target",
|
|
188
|
+
target,
|
|
189
|
+
"-t",
|
|
190
|
+
image_tag,
|
|
191
|
+
"-f",
|
|
192
|
+
dockerfile_abs,
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
# Use native platform for local dev on ARM Macs (avoids slow emulation)
|
|
196
|
+
if platform.machine() == "arm64":
|
|
197
|
+
cmd.extend(["--build-arg", "PLATFORM=linux/arm64"])
|
|
198
|
+
|
|
199
|
+
cmd.append(build_context)
|
|
200
|
+
|
|
201
|
+
logger.debug(f"Build command: {' '.join(cmd)}")
|
|
202
|
+
|
|
203
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
204
|
+
|
|
205
|
+
if result.returncode != 0:
|
|
206
|
+
logger.error(f"Failed to build {image_tag}:\n{result.stderr}")
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
logger.info(f"Successfully built {image_tag}")
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _extract_agent_images_from_config(config_data: dict) -> list[str]:
|
|
214
|
+
"""Extract agent image names from config data.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
config_data: Raw config dictionary
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of image names (without tags) that are local (not from a registry)
|
|
221
|
+
"""
|
|
222
|
+
images = []
|
|
223
|
+
|
|
224
|
+
# Check agents section
|
|
225
|
+
agents = config_data.get("agents", {})
|
|
226
|
+
for agent_config in agents.values():
|
|
227
|
+
if isinstance(agent_config, dict):
|
|
228
|
+
image = agent_config.get("image", "")
|
|
229
|
+
# Only include local images (no registry prefix like ghcr.io/)
|
|
230
|
+
if image and "/" not in image.split(":")[0]:
|
|
231
|
+
# Extract name without tag
|
|
232
|
+
name = image.split(":")[0]
|
|
233
|
+
if name not in images:
|
|
234
|
+
images.append(name)
|
|
235
|
+
|
|
236
|
+
# Also check direct coder/verifier fields
|
|
237
|
+
for field in ["coder", "verifier"]:
|
|
238
|
+
agent_config = config_data.get(field, {})
|
|
239
|
+
if isinstance(agent_config, dict):
|
|
240
|
+
image = agent_config.get("image", "")
|
|
241
|
+
if image and "/" not in image.split(":")[0]:
|
|
242
|
+
name = image.split(":")[0]
|
|
243
|
+
if name not in images:
|
|
244
|
+
images.append(name)
|
|
245
|
+
|
|
246
|
+
return images
|
|
247
|
+
|
|
248
|
+
|
|
138
249
|
async def _run_dev(
|
|
139
250
|
world_name: str,
|
|
140
251
|
config_path: Path,
|
|
141
252
|
env_timeout: int = 600,
|
|
253
|
+
chronos_url: str | None = None,
|
|
254
|
+
api_key: str | None = None,
|
|
255
|
+
agents_dir: Path | None = None,
|
|
142
256
|
) -> None:
|
|
143
257
|
"""Run a world locally with automatic environment creation.
|
|
144
258
|
|
|
145
259
|
This mimics what Chronos does but runs locally for debugging:
|
|
146
260
|
1. Load and parse the config
|
|
147
|
-
2.
|
|
148
|
-
3.
|
|
261
|
+
2. Build local agent images if --agents-dir is provided
|
|
262
|
+
3. Create Plato session with all environments
|
|
263
|
+
4. Optionally initialize Chronos logging for callbacks
|
|
264
|
+
5. Run the world with the session attached
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
world_name: Name of the world to run
|
|
268
|
+
config_path: Path to the config JSON file
|
|
269
|
+
env_timeout: Timeout for environment creation (seconds)
|
|
270
|
+
chronos_url: Optional Chronos base URL for sending log events
|
|
271
|
+
api_key: Optional Plato API key (used for Chronos session creation)
|
|
272
|
+
agents_dir: Optional directory containing agent source code
|
|
149
273
|
"""
|
|
274
|
+
from uuid import uuid4
|
|
275
|
+
|
|
150
276
|
from plato.v2 import AsyncPlato
|
|
151
277
|
from plato.worlds.base import get_world
|
|
152
278
|
|
|
@@ -167,6 +293,23 @@ async def _run_dev(
|
|
|
167
293
|
# Parse the config to get typed access
|
|
168
294
|
run_config = config_class._from_dict(config_data.copy())
|
|
169
295
|
|
|
296
|
+
# Build local agent images if agents_dir is provided
|
|
297
|
+
if agents_dir:
|
|
298
|
+
# Resolve agents_dir to absolute path
|
|
299
|
+
agents_dir = agents_dir.expanduser().resolve()
|
|
300
|
+
agent_images = _extract_agent_images_from_config(config_data)
|
|
301
|
+
if agent_images:
|
|
302
|
+
logger.info(f"Building local agent images: {agent_images}")
|
|
303
|
+
# Determine if we're in a plato-client repo for dev builds
|
|
304
|
+
# (agents_dir is something like /path/to/plato-client/agents)
|
|
305
|
+
plato_client_root = agents_dir.parent if agents_dir.name == "agents" else None
|
|
306
|
+
for agent_name in agent_images:
|
|
307
|
+
success = await _build_agent_image(agent_name, agents_dir, plato_client_root)
|
|
308
|
+
if not success:
|
|
309
|
+
raise RuntimeError(f"Failed to build agent image: {agent_name}")
|
|
310
|
+
else:
|
|
311
|
+
logger.info("No local agent images found in config")
|
|
312
|
+
|
|
170
313
|
# Get environment configs from the parsed config
|
|
171
314
|
env_configs: list[EnvConfig] = run_config.get_envs()
|
|
172
315
|
|
|
@@ -174,6 +317,23 @@ async def _run_dev(
|
|
|
174
317
|
plato = AsyncPlato()
|
|
175
318
|
session = None
|
|
176
319
|
|
|
320
|
+
# Initialize Chronos logging if URL provided
|
|
321
|
+
chronos_session_id: str | None = None
|
|
322
|
+
if chronos_url:
|
|
323
|
+
from plato.agents import init_logging
|
|
324
|
+
|
|
325
|
+
chronos_session_id = f"dev-{uuid4().hex[:8]}"
|
|
326
|
+
callback_url = f"{chronos_url.rstrip('/')}/api/v1/callback"
|
|
327
|
+
init_logging(
|
|
328
|
+
callback_url=callback_url,
|
|
329
|
+
session_id=chronos_session_id,
|
|
330
|
+
)
|
|
331
|
+
logger.info(f"Chronos logging enabled: {callback_url} (session: {chronos_session_id})")
|
|
332
|
+
|
|
333
|
+
# Update run_config with session info for agents
|
|
334
|
+
run_config.session_id = chronos_session_id
|
|
335
|
+
run_config.callback_url = callback_url
|
|
336
|
+
|
|
177
337
|
try:
|
|
178
338
|
if env_configs:
|
|
179
339
|
logger.info(f"Creating {len(env_configs)} environments...")
|
|
@@ -199,12 +359,66 @@ async def _run_dev(
|
|
|
199
359
|
await session.close()
|
|
200
360
|
await plato.close()
|
|
201
361
|
|
|
362
|
+
# Reset logging
|
|
363
|
+
if chronos_url:
|
|
364
|
+
from plato.agents import reset_logging
|
|
365
|
+
|
|
366
|
+
reset_logging()
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _setup_colored_logging(verbose: bool = False) -> None:
|
|
370
|
+
"""Setup colored logging with filtered noisy loggers."""
|
|
371
|
+
log_level = logging.DEBUG if verbose else logging.INFO
|
|
372
|
+
|
|
373
|
+
# Define colors for different log levels
|
|
374
|
+
colors = {
|
|
375
|
+
"DEBUG": "\033[36m", # Cyan
|
|
376
|
+
"INFO": "\033[32m", # Green
|
|
377
|
+
"WARNING": "\033[33m", # Yellow
|
|
378
|
+
"ERROR": "\033[31m", # Red
|
|
379
|
+
"CRITICAL": "\033[35m", # Magenta
|
|
380
|
+
}
|
|
381
|
+
reset = "\033[0m"
|
|
382
|
+
|
|
383
|
+
class ColoredFormatter(logging.Formatter):
|
|
384
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
385
|
+
color = colors.get(record.levelname, "")
|
|
386
|
+
record.levelname = f"{color}{record.levelname}{reset}"
|
|
387
|
+
record.name = f"\033[34m{record.name}{reset}" # Blue for logger name
|
|
388
|
+
return super().format(record)
|
|
389
|
+
|
|
390
|
+
# Create handler with colored formatter
|
|
391
|
+
handler = logging.StreamHandler()
|
|
392
|
+
handler.setFormatter(
|
|
393
|
+
ColoredFormatter(
|
|
394
|
+
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
395
|
+
datefmt="%H:%M:%S",
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Configure root logger
|
|
400
|
+
root_logger = logging.getLogger()
|
|
401
|
+
root_logger.setLevel(log_level)
|
|
402
|
+
root_logger.handlers = [handler]
|
|
403
|
+
|
|
404
|
+
# Silence noisy HTTP loggers
|
|
405
|
+
for noisy_logger in ["httpcore", "httpx", "urllib3", "hpack"]:
|
|
406
|
+
logging.getLogger(noisy_logger).setLevel(logging.WARNING)
|
|
407
|
+
|
|
202
408
|
|
|
203
409
|
@app.command("dev")
|
|
204
410
|
def dev(
|
|
205
411
|
world: Annotated[str, typer.Option("--world", "-w", help="World name to run")],
|
|
206
412
|
config: Annotated[Path, typer.Option("--config", "-c", help="Path to config JSON file")],
|
|
207
413
|
env_timeout: Annotated[int, typer.Option("--env-timeout", help="Timeout for environment creation (seconds)")] = 600,
|
|
414
|
+
chronos_url: Annotated[
|
|
415
|
+
str | None, typer.Option("--chronos-url", help="Chronos base URL for log events (e.g., http://localhost:8000)")
|
|
416
|
+
] = None,
|
|
417
|
+
api_key: Annotated[str | None, typer.Option("--api-key", help="Plato API key for Chronos authentication")] = None,
|
|
418
|
+
agents_dir: Annotated[
|
|
419
|
+
Path | None,
|
|
420
|
+
typer.Option("--agents-dir", "-a", help="Directory containing agent source code (builds local images)"),
|
|
421
|
+
] = None,
|
|
208
422
|
verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Enable verbose logging")] = False,
|
|
209
423
|
) -> None:
|
|
210
424
|
"""Run a world locally for development/debugging.
|
|
@@ -212,30 +426,35 @@ def dev(
|
|
|
212
426
|
This creates Plato environments automatically (like Chronos does)
|
|
213
427
|
and runs the world with the session attached.
|
|
214
428
|
|
|
429
|
+
Optionally sends log events to a Chronos server for real-time monitoring.
|
|
430
|
+
|
|
215
431
|
Example config.json:
|
|
216
432
|
{
|
|
217
|
-
"
|
|
218
|
-
"
|
|
219
|
-
|
|
220
|
-
"
|
|
221
|
-
"image": "openhands:latest",
|
|
222
|
-
"config": {"model_name": "claude-sonnet-4"}
|
|
223
|
-
}
|
|
433
|
+
"instruction": "Create a git repo and upload files to S3",
|
|
434
|
+
"coder": {
|
|
435
|
+
"image": "openhands:latest",
|
|
436
|
+
"config": {"model_name": "gemini/gemini-3-flash-preview"}
|
|
224
437
|
},
|
|
225
438
|
"secrets": {
|
|
226
|
-
"
|
|
439
|
+
"gemini_api_key": "..."
|
|
227
440
|
}
|
|
228
441
|
}
|
|
229
442
|
|
|
230
443
|
Environment variables:
|
|
231
444
|
PLATO_API_KEY: API key for Plato (required)
|
|
445
|
+
|
|
446
|
+
Examples:
|
|
447
|
+
# Basic usage
|
|
448
|
+
plato-world-runner dev -w code -c config.json
|
|
449
|
+
|
|
450
|
+
# With local agent builds (from plato-client repo)
|
|
451
|
+
plato-world-runner dev -w code -c config.json --agents-dir ~/plato-client/agents
|
|
452
|
+
|
|
453
|
+
# With Chronos logging
|
|
454
|
+
plato-world-runner dev -w code -c config.json --chronos-url http://localhost:8000
|
|
232
455
|
"""
|
|
233
|
-
# Setup logging
|
|
234
|
-
|
|
235
|
-
logging.basicConfig(
|
|
236
|
-
level=log_level,
|
|
237
|
-
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
238
|
-
)
|
|
456
|
+
# Setup colored logging with filtered noisy loggers
|
|
457
|
+
_setup_colored_logging(verbose)
|
|
239
458
|
|
|
240
459
|
if not config.exists():
|
|
241
460
|
typer.echo(f"Error: Config file not found: {config}", err=True)
|
|
@@ -246,7 +465,7 @@ def dev(
|
|
|
246
465
|
raise typer.Exit(1)
|
|
247
466
|
|
|
248
467
|
try:
|
|
249
|
-
asyncio.run(_run_dev(world, config, env_timeout))
|
|
468
|
+
asyncio.run(_run_dev(world, config, env_timeout, chronos_url, api_key, agents_dir))
|
|
250
469
|
except Exception as e:
|
|
251
470
|
logger.exception(f"World execution failed: {e}")
|
|
252
471
|
raise typer.Exit(1)
|
|
@@ -302,7 +302,7 @@ plato/agents/base.py,sha256=vUbPQuNSo6Ka2lIB_ZOXgi4EoAjtAD7GIj9LnNotam0,4577
|
|
|
302
302
|
plato/agents/build.py,sha256=CNMbVQFs2_pYit1dA29Davve28Yi4c7TNK9wBB7odrE,1621
|
|
303
303
|
plato/agents/config.py,sha256=VZVMdCmEQnoR0VkrGdScG8p6zSKVFe7BZPd2h8lKNjI,5460
|
|
304
304
|
plato/agents/logging.py,sha256=z9rDlGPbrpcTS8PephbK2rDqT7thC1KyLkua4ypUkv4,12210
|
|
305
|
-
plato/agents/runner.py,sha256=
|
|
305
|
+
plato/agents/runner.py,sha256=YoqG1QdNScIjSSH0vPgnm42LlqeAeVsFT01VL77ony0,5565
|
|
306
306
|
plato/agents/trajectory.py,sha256=WdiBmua0KvCrNaM3qgPI7-7B4xmSkfbP4oZ_9_8qHzU,10529
|
|
307
307
|
plato/chronos/__init__.py,sha256=RHMvSrQS_-vkKOyTRuAkp2gKDP1HEuBLDnw8jcZs1Jg,739
|
|
308
308
|
plato/chronos/client.py,sha256=YcOGtHWERyOD9z8LKt8bRMVL0cEwL2hiAP4qQgdZlUI,5495
|
|
@@ -464,11 +464,11 @@ plato/v2/utils/models.py,sha256=PwehSSnIRG-tM3tWL1PzZEH77ZHhIAZ9R0UPs6YknbM,1441
|
|
|
464
464
|
plato/v2/utils/proxy_tunnel.py,sha256=8ZTd0jCGSfIHMvSv1fgEyacuISWnGPHLPbDglWroTzY,10463
|
|
465
465
|
plato/worlds/README.md,sha256=TgG4aidude0ouJSCfY81Ev45hsUxPkO85HUIiWNqkcc,5463
|
|
466
466
|
plato/worlds/__init__.py,sha256=crzpXFh4XD8eS4pYFTEUf3XgUf0wapFPT4npAu8sWwk,2078
|
|
467
|
-
plato/worlds/base.py,sha256=
|
|
467
|
+
plato/worlds/base.py,sha256=5_BAad_w0l4DkgnNYojSDNfOWHXLmD0Q39cLSUuGYJw,10795
|
|
468
468
|
plato/worlds/build_hook.py,sha256=KSoW0kqa5b7NyZ7MYOw2qsZ_2FkWuz0M3Ru7AKOP7Qw,3486
|
|
469
469
|
plato/worlds/config.py,sha256=ggDcySspfeFry2KBUwhgnS6Po2KssYzwZNIDmVeGhPQ,9460
|
|
470
|
-
plato/worlds/runner.py,sha256=
|
|
471
|
-
plato_sdk_v2-2.2.
|
|
472
|
-
plato_sdk_v2-2.2.
|
|
473
|
-
plato_sdk_v2-2.2.
|
|
474
|
-
plato_sdk_v2-2.2.
|
|
470
|
+
plato/worlds/runner.py,sha256=RNnWFQ7rfEWE7TQ_tqgLHgLm1a4VxtP0mR7beALx4f0,15781
|
|
471
|
+
plato_sdk_v2-2.2.3.dist-info/METADATA,sha256=18iVpDD44_fE6Sy6U19MKBNCI1zDY-0fk7CZ0SlC8ak,8508
|
|
472
|
+
plato_sdk_v2-2.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
473
|
+
plato_sdk_v2-2.2.3.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
|
|
474
|
+
plato_sdk_v2-2.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|