plato-sdk-v2 2.3.7__py3-none-any.whl → 2.3.8__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/otel.py +16 -6
- plato/v1/cli/sandbox.py +5 -2
- plato/v1/cli/ssh.py +21 -14
- plato/v1/cli/utils.py +32 -12
- plato/worlds/base.py +16 -0
- {plato_sdk_v2-2.3.7.dist-info → plato_sdk_v2-2.3.8.dist-info}/METADATA +1 -2
- {plato_sdk_v2-2.3.7.dist-info → plato_sdk_v2-2.3.8.dist-info}/RECORD +9 -9
- {plato_sdk_v2-2.3.7.dist-info → plato_sdk_v2-2.3.8.dist-info}/WHEEL +0 -0
- {plato_sdk_v2-2.3.7.dist-info → plato_sdk_v2-2.3.8.dist-info}/entry_points.txt +0 -0
plato/agents/otel.py
CHANGED
|
@@ -51,6 +51,8 @@ class OTelSpanLogHandler(logging.Handler):
|
|
|
51
51
|
def emit(self, record: logging.LogRecord) -> None:
|
|
52
52
|
"""Emit a log record as an OTel span."""
|
|
53
53
|
try:
|
|
54
|
+
# Debug: print that we're emitting a log span
|
|
55
|
+
print(f"[OTel] Emitting log span: {record.name} - {record.getMessage()[:100]}")
|
|
54
56
|
# Create a span for the log message
|
|
55
57
|
with self.tracer.start_as_current_span(f"log.{record.levelname.lower()}") as span:
|
|
56
58
|
span.set_attribute("log.level", record.levelname)
|
|
@@ -137,13 +139,18 @@ def init_tracing(
|
|
|
137
139
|
context_api.attach(ctx)
|
|
138
140
|
print(f"[OTel] Using parent context: trace_id={parent_trace_id}, span_id={parent_span_id}")
|
|
139
141
|
|
|
140
|
-
# Add OTel logging handler to capture
|
|
142
|
+
# Add OTel logging handler to capture logs from plato SDK
|
|
141
143
|
tracer = trace.get_tracer(service_name)
|
|
142
144
|
_log_handler = OTelSpanLogHandler(tracer, level=logging.INFO)
|
|
143
145
|
|
|
144
|
-
# Add handler to plato
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
# Add handler to plato loggers (worlds and agents)
|
|
147
|
+
# Set level to INFO to ensure logs propagate from child loggers
|
|
148
|
+
plato_logger = logging.getLogger("plato")
|
|
149
|
+
plato_logger.setLevel(logging.INFO)
|
|
150
|
+
plato_logger.addHandler(_log_handler)
|
|
151
|
+
print(
|
|
152
|
+
f"[OTel] Added log handler to 'plato' logger (level={plato_logger.level}, handlers={len(plato_logger.handlers)})"
|
|
153
|
+
)
|
|
147
154
|
|
|
148
155
|
_initialized = True
|
|
149
156
|
|
|
@@ -164,8 +171,8 @@ def shutdown_tracing() -> None:
|
|
|
164
171
|
# Remove log handler
|
|
165
172
|
if _log_handler:
|
|
166
173
|
try:
|
|
167
|
-
|
|
168
|
-
|
|
174
|
+
plato_logger = logging.getLogger("plato")
|
|
175
|
+
plato_logger.removeHandler(_log_handler)
|
|
169
176
|
except Exception:
|
|
170
177
|
pass
|
|
171
178
|
_log_handler = None
|
|
@@ -222,8 +229,11 @@ def instrument(service_name: str = "plato-agent") -> Tracer:
|
|
|
222
229
|
parent_trace_id = os.environ.get("OTEL_TRACE_ID")
|
|
223
230
|
parent_span_id = os.environ.get("OTEL_PARENT_SPAN_ID")
|
|
224
231
|
|
|
232
|
+
print(f"[OTel] instrument() called: service={service_name}, endpoint={otel_endpoint}, session={session_id}")
|
|
233
|
+
|
|
225
234
|
if not otel_endpoint:
|
|
226
235
|
# Return default tracer (no-op if no provider configured)
|
|
236
|
+
print("[OTel] No OTEL_EXPORTER_OTLP_ENDPOINT set, returning no-op tracer")
|
|
227
237
|
return trace.get_tracer(service_name)
|
|
228
238
|
|
|
229
239
|
# Initialize tracing with parent context if provided
|
plato/v1/cli/sandbox.py
CHANGED
|
@@ -131,6 +131,9 @@ def sandbox_start(
|
|
|
131
131
|
timeout: int = typer.Option(1800, "--timeout", help="VM lifetime in seconds (default: 30 minutes)"),
|
|
132
132
|
no_reset: bool = typer.Option(False, "--no-reset", help="Skip initial reset after ready"),
|
|
133
133
|
json_output: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
134
|
+
working_dir: Path = typer.Option(
|
|
135
|
+
None, "--working-dir", "-w", help="Working directory for .sandbox.yaml and .plato/"
|
|
136
|
+
),
|
|
134
137
|
):
|
|
135
138
|
"""
|
|
136
139
|
Start a sandbox environment.
|
|
@@ -377,7 +380,7 @@ def sandbox_start(
|
|
|
377
380
|
console.print("[cyan] Generating SSH key pair...[/cyan]")
|
|
378
381
|
|
|
379
382
|
base_url = os.getenv("PLATO_BASE_URL", "https://plato.so")
|
|
380
|
-
ssh_info = setup_ssh_for_sandbox(base_url, job_id, username=ssh_username)
|
|
383
|
+
ssh_info = setup_ssh_for_sandbox(base_url, job_id, username=ssh_username, working_dir=working_dir)
|
|
381
384
|
ssh_host = ssh_info["ssh_host"]
|
|
382
385
|
ssh_config_path = ssh_info["config_path"]
|
|
383
386
|
ssh_private_key_path = ssh_info["private_key_path"]
|
|
@@ -489,7 +492,7 @@ def sandbox_start(
|
|
|
489
492
|
# Add heartbeat PID
|
|
490
493
|
if heartbeat_pid:
|
|
491
494
|
state["heartbeat_pid"] = heartbeat_pid
|
|
492
|
-
save_sandbox_state(state)
|
|
495
|
+
save_sandbox_state(state, working_dir)
|
|
493
496
|
|
|
494
497
|
# Close the plato client (heartbeat process keeps session alive)
|
|
495
498
|
plato.close()
|
plato/v1/cli/ssh.py
CHANGED
|
@@ -9,21 +9,21 @@ from cryptography.hazmat.primitives import serialization
|
|
|
9
9
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def get_plato_dir() -> Path:
|
|
12
|
+
def get_plato_dir(working_dir: Path | str | None = None) -> Path:
|
|
13
13
|
"""Get the directory for plato config/SSH files.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Args:
|
|
16
|
+
working_dir: If provided, returns working_dir/.plato (for container/agent use).
|
|
17
|
+
If None, returns ~/.plato (local development).
|
|
17
18
|
"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return workspace / ".plato"
|
|
19
|
+
if working_dir is not None:
|
|
20
|
+
return Path(working_dir) / ".plato"
|
|
21
21
|
return Path.home() / ".plato"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def get_next_sandbox_number() -> int:
|
|
24
|
+
def get_next_sandbox_number(working_dir: Path | str | None = None) -> int:
|
|
25
25
|
"""Find next available sandbox number by checking existing config files."""
|
|
26
|
-
plato_dir = get_plato_dir()
|
|
26
|
+
plato_dir = get_plato_dir(working_dir)
|
|
27
27
|
if not plato_dir.exists():
|
|
28
28
|
return 1
|
|
29
29
|
|
|
@@ -41,13 +41,13 @@ def get_next_sandbox_number() -> int:
|
|
|
41
41
|
return max_num + 1
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def generate_ssh_key_pair(sandbox_num: int) -> tuple[str, str]:
|
|
44
|
+
def generate_ssh_key_pair(sandbox_num: int, working_dir: Path | str | None = None) -> tuple[str, str]:
|
|
45
45
|
"""
|
|
46
46
|
Generate a new ed25519 SSH key pair for a specific sandbox.
|
|
47
47
|
|
|
48
48
|
Returns (public_key_str, private_key_path).
|
|
49
49
|
"""
|
|
50
|
-
plato_dir = get_plato_dir()
|
|
50
|
+
plato_dir = get_plato_dir(working_dir)
|
|
51
51
|
plato_dir.mkdir(mode=0o700, exist_ok=True)
|
|
52
52
|
|
|
53
53
|
private_key_path = plato_dir / f"ssh_{sandbox_num}_key"
|
|
@@ -136,6 +136,7 @@ def create_ssh_config(
|
|
|
136
136
|
username: str,
|
|
137
137
|
private_key_path: str,
|
|
138
138
|
sandbox_num: int,
|
|
139
|
+
working_dir: Path | str | None = None,
|
|
139
140
|
) -> str:
|
|
140
141
|
"""
|
|
141
142
|
Create a temporary SSH config file for a specific sandbox.
|
|
@@ -172,7 +173,7 @@ def create_ssh_config(
|
|
|
172
173
|
TCPKeepAlive yes
|
|
173
174
|
"""
|
|
174
175
|
|
|
175
|
-
plato_dir = get_plato_dir()
|
|
176
|
+
plato_dir = get_plato_dir(working_dir)
|
|
176
177
|
plato_dir.mkdir(mode=0o700, exist_ok=True)
|
|
177
178
|
|
|
178
179
|
config_path = plato_dir / f"ssh_{sandbox_num}.conf"
|
|
@@ -182,7 +183,12 @@ def create_ssh_config(
|
|
|
182
183
|
return str(config_path)
|
|
183
184
|
|
|
184
185
|
|
|
185
|
-
def setup_ssh_for_sandbox(
|
|
186
|
+
def setup_ssh_for_sandbox(
|
|
187
|
+
base_url: str,
|
|
188
|
+
job_public_id: str,
|
|
189
|
+
username: str = "plato",
|
|
190
|
+
working_dir: Path | str | None = None,
|
|
191
|
+
) -> dict:
|
|
186
192
|
"""
|
|
187
193
|
Set up SSH access for a sandbox - generates keys and creates config.
|
|
188
194
|
|
|
@@ -190,14 +196,14 @@ def setup_ssh_for_sandbox(base_url: str, job_public_id: str, username: str = "pl
|
|
|
190
196
|
|
|
191
197
|
Returns dict with: ssh_host, config_path, public_key, private_key_path
|
|
192
198
|
"""
|
|
193
|
-
sandbox_num = get_next_sandbox_number()
|
|
199
|
+
sandbox_num = get_next_sandbox_number(working_dir)
|
|
194
200
|
ssh_host = f"sandbox-{sandbox_num}"
|
|
195
201
|
|
|
196
202
|
# Choose random port between 2200 and 2299
|
|
197
203
|
local_port = random.randint(2200, 2299)
|
|
198
204
|
|
|
199
205
|
# Generate SSH key pair
|
|
200
|
-
public_key, private_key_path = generate_ssh_key_pair(sandbox_num)
|
|
206
|
+
public_key, private_key_path = generate_ssh_key_pair(sandbox_num, working_dir)
|
|
201
207
|
|
|
202
208
|
# Create SSH config file
|
|
203
209
|
config_path = create_ssh_config(
|
|
@@ -208,6 +214,7 @@ def setup_ssh_for_sandbox(base_url: str, job_public_id: str, username: str = "pl
|
|
|
208
214
|
username=username,
|
|
209
215
|
private_key_path=private_key_path,
|
|
210
216
|
sandbox_num=sandbox_num,
|
|
217
|
+
working_dir=working_dir,
|
|
211
218
|
)
|
|
212
219
|
|
|
213
220
|
return {
|
plato/v1/cli/utils.py
CHANGED
|
@@ -15,32 +15,52 @@ console = Console()
|
|
|
15
15
|
SANDBOX_FILE = ".sandbox.yaml"
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def get_sandbox_state() -> dict | None:
|
|
19
|
-
"""Read sandbox state from .sandbox.yaml
|
|
20
|
-
|
|
18
|
+
def get_sandbox_state(working_dir: Path | str | None = None) -> dict | None:
|
|
19
|
+
"""Read sandbox state from .sandbox.yaml.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
working_dir: Directory containing .sandbox.yaml. If None, uses cwd.
|
|
23
|
+
"""
|
|
24
|
+
base_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
25
|
+
sandbox_file = base_dir / SANDBOX_FILE
|
|
21
26
|
if not sandbox_file.exists():
|
|
22
27
|
return None
|
|
23
28
|
with open(sandbox_file) as f:
|
|
24
29
|
return yaml.safe_load(f)
|
|
25
30
|
|
|
26
31
|
|
|
27
|
-
def save_sandbox_state(state: dict) -> None:
|
|
28
|
-
"""Save sandbox state to .sandbox.yaml
|
|
29
|
-
|
|
32
|
+
def save_sandbox_state(state: dict, working_dir: Path | str | None = None) -> None:
|
|
33
|
+
"""Save sandbox state to .sandbox.yaml.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
state: State dict to save.
|
|
37
|
+
working_dir: Directory to save .sandbox.yaml in. If None, uses cwd.
|
|
38
|
+
"""
|
|
39
|
+
base_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
40
|
+
sandbox_file = base_dir / SANDBOX_FILE
|
|
30
41
|
with open(sandbox_file, "w") as f:
|
|
31
42
|
yaml.dump(state, f, default_flow_style=False)
|
|
32
43
|
|
|
33
44
|
|
|
34
|
-
def remove_sandbox_state() -> None:
|
|
35
|
-
"""Remove .sandbox.yaml
|
|
36
|
-
|
|
45
|
+
def remove_sandbox_state(working_dir: Path | str | None = None) -> None:
|
|
46
|
+
"""Remove .sandbox.yaml.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
working_dir: Directory containing .sandbox.yaml. If None, uses cwd.
|
|
50
|
+
"""
|
|
51
|
+
base_dir = Path(working_dir) if working_dir else Path.cwd()
|
|
52
|
+
sandbox_file = base_dir / SANDBOX_FILE
|
|
37
53
|
if sandbox_file.exists():
|
|
38
54
|
sandbox_file.unlink()
|
|
39
55
|
|
|
40
56
|
|
|
41
|
-
def require_sandbox_state() -> dict:
|
|
42
|
-
"""Get sandbox state or exit with error.
|
|
43
|
-
|
|
57
|
+
def require_sandbox_state(working_dir: Path | str | None = None) -> dict:
|
|
58
|
+
"""Get sandbox state or exit with error.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
working_dir: Directory containing .sandbox.yaml. If None, uses cwd.
|
|
62
|
+
"""
|
|
63
|
+
state = get_sandbox_state(working_dir)
|
|
44
64
|
if not state:
|
|
45
65
|
console.print("[red]No sandbox found in current directory[/red]")
|
|
46
66
|
console.print("\n[yellow]Start a sandbox with:[/yellow]")
|
plato/worlds/base.py
CHANGED
|
@@ -28,6 +28,17 @@ from plato.agents.otel import (
|
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
def _get_plato_version() -> str:
|
|
33
|
+
"""Get the installed plato SDK version."""
|
|
34
|
+
try:
|
|
35
|
+
from importlib.metadata import version
|
|
36
|
+
|
|
37
|
+
return version("plato")
|
|
38
|
+
except Exception:
|
|
39
|
+
return "unknown"
|
|
40
|
+
|
|
41
|
+
|
|
31
42
|
# Global registry of worlds
|
|
32
43
|
_WORLD_REGISTRY: dict[str, type[BaseWorld]] = {}
|
|
33
44
|
|
|
@@ -673,6 +684,11 @@ The following services are available for your use:
|
|
|
673
684
|
else:
|
|
674
685
|
logger.debug("No otel_url in config - OTel tracing disabled")
|
|
675
686
|
|
|
687
|
+
# Log version info (goes to OTel after init_tracing)
|
|
688
|
+
plato_version = _get_plato_version()
|
|
689
|
+
world_version = self.get_version()
|
|
690
|
+
self.logger.info(f"World version: {world_version}, Plato SDK version: {plato_version}")
|
|
691
|
+
|
|
676
692
|
# Connect to Plato session if configured (for heartbeats)
|
|
677
693
|
await self._connect_plato_session()
|
|
678
694
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plato-sdk-v2
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.8
|
|
4
4
|
Summary: Python SDK for the Plato API
|
|
5
5
|
Author-email: Plato <support@plato.so>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,7 +47,6 @@ Description-Content-Type: text/markdown
|
|
|
47
47
|
|
|
48
48
|
# Plato Python SDK
|
|
49
49
|
|
|
50
|
-
|
|
51
50
|
Python SDK for the Plato platform. Uses [Harbor](https://harborframework.com) for agent execution.
|
|
52
51
|
|
|
53
52
|
## Installation
|
|
@@ -302,7 +302,7 @@ plato/agents/artifacts.py,sha256=ljeI0wzsp7Q6uKqMb-k7kTb680Vizs54ohtM-d7zvOg,292
|
|
|
302
302
|
plato/agents/base.py,sha256=vUbPQuNSo6Ka2lIB_ZOXgi4EoAjtAD7GIj9LnNotam0,4577
|
|
303
303
|
plato/agents/build.py,sha256=CNMbVQFs2_pYit1dA29Davve28Yi4c7TNK9wBB7odrE,1621
|
|
304
304
|
plato/agents/config.py,sha256=CmRS6vOAg7JeqX4Hgp_KpA1YWBX_LuMicHm7SBjQEbs,5077
|
|
305
|
-
plato/agents/otel.py,sha256=
|
|
305
|
+
plato/agents/otel.py,sha256=UOEBeMyyfbffsC3tRXlHAydoj9bXwJupA_UeLZ5h97w,8585
|
|
306
306
|
plato/agents/runner.py,sha256=Ei20Ib-Fn5XOaS6V1Rtw0UEw34XflEWaXMpazPjmnrE,6061
|
|
307
307
|
plato/agents/trajectory.py,sha256=WdiBmua0KvCrNaM3qgPI7-7B4xmSkfbP4oZ_9_8qHzU,10529
|
|
308
308
|
plato/chronos/__init__.py,sha256=RHMvSrQS_-vkKOyTRuAkp2gKDP1HEuBLDnw8jcZs1Jg,739
|
|
@@ -389,9 +389,9 @@ plato/v1/cli/__init__.py,sha256=om4b7PxgsoI7rEwuQelmQkqPdhMVn53_5qEN8kvksYw,105
|
|
|
389
389
|
plato/v1/cli/agent.py,sha256=G6TV3blG_BqMDBWS-CG7GwzqoqcJTMsIKQ88jvLXb4k,43745
|
|
390
390
|
plato/v1/cli/main.py,sha256=ktPtBvMwykR7AjXmTQ6bmZkHdzpAjhX5Fq66cDbGSzA,6844
|
|
391
391
|
plato/v1/cli/pm.py,sha256=uLM6WszKqxq9Czg1FraDyWb9_INUuHZq63imvRYfRLw,49734
|
|
392
|
-
plato/v1/cli/sandbox.py,sha256=
|
|
393
|
-
plato/v1/cli/ssh.py,sha256=
|
|
394
|
-
plato/v1/cli/utils.py,sha256=
|
|
392
|
+
plato/v1/cli/sandbox.py,sha256=N7DIpXsxExtZB47tWKxp-3cV0_gLnI7C-mTKW3tTi8Y,95360
|
|
393
|
+
plato/v1/cli/ssh.py,sha256=enrf7Y01ZeRIyHDEX0Yt7up5zEe7MCvE9u8SP4Oqiz4,6926
|
|
394
|
+
plato/v1/cli/utils.py,sha256=ba7Crv4OjDmgCv4SeB8UeZDin-iOdQw_3N6fd-g5XVk,4572
|
|
395
395
|
plato/v1/cli/verify.py,sha256=7QmQwfOOkr8a51f8xfVIr2zif7wGl2E8HOZTbOaIoV0,20671
|
|
396
396
|
plato/v1/cli/world.py,sha256=yBUadOJs1QYm6Jmx_ACDzogybRq5x4B-BnTvGO_ulQk,9757
|
|
397
397
|
plato/v1/examples/doordash_tasks.py,sha256=8Sz9qx-vTmiOAiCAbrDRvZGsA1qQQBr1KHbxXdjr7OI,23233
|
|
@@ -458,11 +458,11 @@ plato/v2/utils/models.py,sha256=PwehSSnIRG-tM3tWL1PzZEH77ZHhIAZ9R0UPs6YknbM,1441
|
|
|
458
458
|
plato/v2/utils/proxy_tunnel.py,sha256=8ZTd0jCGSfIHMvSv1fgEyacuISWnGPHLPbDglWroTzY,10463
|
|
459
459
|
plato/worlds/README.md,sha256=XFOkEA3cNNcrWkk-Cxnsl-zn-y0kvUENKQRSqFKpdqw,5479
|
|
460
460
|
plato/worlds/__init__.py,sha256=ALoou3l5lXvs_YZc5eH6HdMHpvhnpzKWqz__aSC1jFc,2152
|
|
461
|
-
plato/worlds/base.py,sha256=
|
|
461
|
+
plato/worlds/base.py,sha256=kIX02gMQR43ZsZK25vZvCoNkYtICVRMeofNk-im5IRM,28215
|
|
462
462
|
plato/worlds/build_hook.py,sha256=KSoW0kqa5b7NyZ7MYOw2qsZ_2FkWuz0M3Ru7AKOP7Qw,3486
|
|
463
463
|
plato/worlds/config.py,sha256=a5frj3mt06rSlT25kE-L8Q2b2MTWkR-8cUoBKpC8tG4,11036
|
|
464
464
|
plato/worlds/runner.py,sha256=2H5EV77bTYrMyI7qez0kwxOp9EApQxG19Ob9a_GTdbw,19383
|
|
465
|
-
plato_sdk_v2-2.3.
|
|
466
|
-
plato_sdk_v2-2.3.
|
|
467
|
-
plato_sdk_v2-2.3.
|
|
468
|
-
plato_sdk_v2-2.3.
|
|
465
|
+
plato_sdk_v2-2.3.8.dist-info/METADATA,sha256=GnoZAAlPmFfWxSwHFjkab7s6MmWfk8rXOvOynTb2r28,8652
|
|
466
|
+
plato_sdk_v2-2.3.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
467
|
+
plato_sdk_v2-2.3.8.dist-info/entry_points.txt,sha256=upGMbJCx6YWUTKrPoYvYUYfFCqYr75nHDwhA-45m6p8,136
|
|
468
|
+
plato_sdk_v2-2.3.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|