scalebox-sdk 0.1.24__py3-none-any.whl → 1.0.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 (87) hide show
  1. scalebox/__init__.py +2 -2
  2. scalebox/api/__init__.py +130 -128
  3. scalebox/api/client/__init__.py +8 -8
  4. scalebox/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +2 -2
  5. scalebox/api/client/api/sandboxes/post_sandboxes.py +2 -2
  6. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  7. scalebox/api/client/client.py +288 -288
  8. scalebox/api/client/models/connect_sandbox.py +59 -0
  9. scalebox/api/client/models/error.py +2 -2
  10. scalebox/api/client/models/listed_sandbox.py +19 -1
  11. scalebox/api/client/models/new_sandbox.py +10 -0
  12. scalebox/api/client/models/sandbox.py +138 -125
  13. scalebox/api/client/models/sandbox_detail.py +24 -0
  14. scalebox/api/client/types.py +46 -46
  15. scalebox/cli.py +125 -125
  16. scalebox/client/aclient.py +57 -57
  17. scalebox/client/client.py +102 -102
  18. scalebox/code_interpreter/__init__.py +12 -12
  19. scalebox/code_interpreter/charts.py +230 -230
  20. scalebox/code_interpreter/constants.py +3 -3
  21. scalebox/code_interpreter/exceptions.py +13 -13
  22. scalebox/code_interpreter/models.py +485 -485
  23. scalebox/connection_config.py +34 -1
  24. scalebox/csx_connect/__init__.py +1 -1
  25. scalebox/csx_connect/client.py +485 -485
  26. scalebox/csx_desktop/main.py +651 -651
  27. scalebox/exceptions.py +83 -83
  28. scalebox/generated/api.py +61 -61
  29. scalebox/generated/api_pb2.py +203 -203
  30. scalebox/generated/api_pb2.pyi +956 -956
  31. scalebox/generated/api_pb2_connect.py +1407 -1407
  32. scalebox/generated/rpc.py +50 -50
  33. scalebox/sandbox/main.py +146 -139
  34. scalebox/sandbox/sandbox_api.py +105 -91
  35. scalebox/sandbox/signature.py +40 -40
  36. scalebox/sandbox/utils.py +34 -34
  37. scalebox/sandbox_async/commands/command.py +307 -307
  38. scalebox/sandbox_async/commands/command_handle.py +187 -187
  39. scalebox/sandbox_async/commands/pty.py +187 -187
  40. scalebox/sandbox_async/filesystem/filesystem.py +557 -557
  41. scalebox/sandbox_async/filesystem/watch_handle.py +61 -61
  42. scalebox/sandbox_async/main.py +228 -46
  43. scalebox/sandbox_async/sandbox_api.py +124 -3
  44. scalebox/sandbox_async/utils.py +7 -7
  45. scalebox/sandbox_sync/__init__.py +2 -2
  46. scalebox/sandbox_sync/commands/command.py +300 -300
  47. scalebox/sandbox_sync/commands/command_handle.py +150 -150
  48. scalebox/sandbox_sync/commands/pty.py +181 -181
  49. scalebox/sandbox_sync/filesystem/filesystem.py +3 -3
  50. scalebox/sandbox_sync/filesystem/watch_handle.py +66 -66
  51. scalebox/sandbox_sync/main.py +208 -133
  52. scalebox/sandbox_sync/sandbox_api.py +119 -3
  53. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
  54. scalebox/test/README.md +329 -329
  55. scalebox/test/bedrock_openai_adapter.py +67 -0
  56. scalebox/test/code_interpreter_test.py +34 -34
  57. scalebox/test/code_interpreter_test_sync.py +34 -34
  58. scalebox/test/run_stress_code_interpreter_sync.py +166 -0
  59. scalebox/test/simple_upload_example.py +123 -0
  60. scalebox/test/stabitiy_test.py +310 -0
  61. scalebox/test/test_browser_use.py +25 -0
  62. scalebox/test/test_browser_use_scalebox.py +61 -0
  63. scalebox/test/test_code_interpreter_sync_comprehensive.py +115 -65
  64. scalebox/test/test_connect_pause_async.py +277 -0
  65. scalebox/test/test_connect_pause_sync.py +267 -0
  66. scalebox/test/test_desktop_sandbox_sf.py +117 -0
  67. scalebox/test/test_download_url.py +49 -0
  68. scalebox/test/test_sandbox_async_comprehensive.py +1 -1
  69. scalebox/test/test_sandbox_object_storage_example.py +146 -0
  70. scalebox/test/test_sandbox_object_storage_example_async.py +156 -0
  71. scalebox/test/test_sf.py +137 -0
  72. scalebox/test/test_watch_dir_async.py +56 -0
  73. scalebox/test/testacreate.py +1 -1
  74. scalebox/test/testagetinfo.py +1 -1
  75. scalebox/test/testcomputeuse.py +243 -243
  76. scalebox/test/testsandbox_api.py +13 -0
  77. scalebox/test/testsandbox_sync.py +1 -1
  78. scalebox/test/upload_100mb_example.py +355 -0
  79. scalebox/utils/httpcoreclient.py +297 -297
  80. scalebox/utils/httpxclient.py +403 -403
  81. scalebox/version.py +2 -2
  82. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/METADATA +1 -1
  83. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/RECORD +87 -69
  84. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/WHEEL +1 -1
  85. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/entry_points.txt +0 -0
  86. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
  87. {scalebox_sdk-0.1.24.dist-info → scalebox_sdk-1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
- from typing import Awaitable, Callable, TypeVar, Union
2
-
3
- T = TypeVar("T")
4
- OutputHandler = Union[
5
- Callable[[T], None],
6
- Callable[[T], Awaitable[None]],
7
- ]
1
+ from typing import Awaitable, Callable, TypeVar, Union
2
+
3
+ T = TypeVar("T")
4
+ OutputHandler = Union[
5
+ Callable[[T], None],
6
+ Callable[[T], Awaitable[None]],
7
+ ]
@@ -1,2 +1,2 @@
1
- from .commands.command_handle import CommandExitException, CommandHandle, CommandResult
2
- from .main import Sandbox
1
+ from .commands.command_handle import CommandExitException, CommandHandle, CommandResult
2
+ from .main import Sandbox
@@ -1,300 +1,300 @@
1
- from typing import Callable, Dict, List, Literal, Optional, Union, overload
2
-
3
- import httpcore
4
- import urllib3
5
-
6
- from ... import csx_connect
7
- from ...connection_config import (
8
- KEEPALIVE_PING_HEADER,
9
- KEEPALIVE_PING_INTERVAL_SEC,
10
- ConnectionConfig,
11
- Username,
12
- )
13
- from ...exceptions import SandboxException
14
- from ...generated import api_pb2, api_pb2_connect
15
- from ...generated.rpc import authentication_header, handle_rpc_exception
16
- from ...sandbox.commands.command_handle import CommandResult
17
- from ...sandbox.commands.main import ProcessInfo
18
- from ...sandbox_sync.commands.command_handle import CommandHandle
19
-
20
-
21
- class Commands:
22
- """
23
- Module for executing commands in the sandbox.
24
- """
25
-
26
- def __init__(
27
- self,
28
- envd_api_url: str,
29
- connection_config: ConnectionConfig,
30
- pool: urllib3.PoolManager,
31
- ) -> None:
32
- self._connection_config = connection_config
33
- self._rpc = api_pb2_connect.ProcessClient(
34
- envd_api_url,
35
- http_client=pool,
36
- )
37
- self._headers = connection_config.headers
38
- self._pool = pool
39
-
40
- def list(
41
- self,
42
- request_timeout: Optional[float] = None,
43
- ) -> List[ProcessInfo]:
44
- """
45
- Lists all running commands and PTY sessions.
46
-
47
- :param request_timeout: Timeout for the request in **seconds**
48
-
49
- :return: List of running commands and PTY sessions
50
- """
51
- try:
52
- res = self._rpc.list(
53
- api_pb2.ListRequest(),
54
- self._headers,
55
- timeout_seconds=self._connection_config.get_request_timeout(
56
- request_timeout
57
- ),
58
- )
59
- return [
60
- ProcessInfo(
61
- pid=p.pid,
62
- tag=p.tag,
63
- cmd=p.config.cmd,
64
- args=list(p.config.args),
65
- envs=dict(p.config.envs),
66
- cwd=p.config.cwd,
67
- )
68
- for p in res.processes
69
- ]
70
- except Exception as e:
71
- raise handle_rpc_exception(e)
72
-
73
- def kill(
74
- self,
75
- pid: int,
76
- request_timeout: Optional[float] = None,
77
- ) -> bool:
78
- """
79
- Kills a running command specified by its process ID.
80
- It uses `SIGKILL` signal to kill the command.
81
-
82
- :param pid: Process ID of the command. You can get the list of processes using `sandbox.commands.list()`
83
- :param request_timeout: Timeout for the request in **seconds**
84
-
85
- :return: `True` if the command was killed, `False` if the command was not found
86
- """
87
- try:
88
- self._rpc.send_signal(
89
- api_pb2.SendSignalRequest(
90
- process=api_pb2.ProcessSelector(pid=pid),
91
- signal=api_pb2.Signal.SIGNAL_SIGKILL,
92
- ),
93
- self._headers,
94
- timeout_seconds=self._connection_config.get_request_timeout(
95
- request_timeout
96
- ),
97
- )
98
- return True
99
- except Exception as e:
100
- if "not found" in str(e):
101
- return False
102
- raise handle_rpc_exception(e)
103
-
104
- def send_stdin(
105
- self,
106
- pid: int,
107
- data: str,
108
- request_timeout: Optional[float] = None,
109
- ):
110
- """
111
- Send data to command stdin.
112
-
113
- :param pid Process ID of the command. You can get the list of processes using `sandbox.commands.list()`.
114
- :param data: Data to send to the command
115
- :param request_timeout: Timeout for the request in **seconds**
116
- """
117
- try:
118
- self._rpc.send_input(
119
- api_pb2.SendInputRequest(
120
- process=api_pb2.ProcessSelector(pid=pid),
121
- input=api_pb2.ProcessInput(
122
- stdin=data.encode(),
123
- ),
124
- ),
125
- self._headers,
126
- timeout_seconds=self._connection_config.get_request_timeout(
127
- request_timeout
128
- ),
129
- )
130
- except Exception as e:
131
- raise handle_rpc_exception(e)
132
-
133
- @overload
134
- def run(
135
- self,
136
- cmd: str,
137
- background: Union[Literal[False], None] = None,
138
- envs: Optional[Dict[str, str]] = None,
139
- user: Username = "user",
140
- cwd: Optional[str] = None,
141
- on_stdout: Optional[Callable[[str], None]] = None,
142
- on_stderr: Optional[Callable[[str], None]] = None,
143
- timeout: Optional[float] = 60,
144
- request_timeout: Optional[float] = None,
145
- ) -> CommandResult:
146
- """
147
- Start a new command and wait until it finishes executing.
148
-
149
- :param cmd: Command to execute
150
- :param background: **`False` if the command should be executed in the foreground**, `True` if the command should be executed in the background
151
- :param envs: Environment variables used for the command
152
- :param user: User to run the command as
153
- :param cwd: Working directory to run the command
154
- :param on_stdout: Callback for command stdout output
155
- :param on_stderr: Callback for command stderr output
156
- :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
157
- :param request_timeout: Timeout for the request in **seconds**
158
-
159
- :return: `CommandResult` result of the command execution
160
- """
161
- ...
162
-
163
- @overload
164
- def run(
165
- self,
166
- cmd: str,
167
- background: Literal[True],
168
- envs: Optional[Dict[str, str]] = None,
169
- user: Username = "user",
170
- cwd: Optional[str] = None,
171
- on_stdout: None = None,
172
- on_stderr: None = None,
173
- timeout: Optional[float] = 60,
174
- request_timeout: Optional[float] = None,
175
- ) -> CommandHandle:
176
- """
177
- Start a new command and return a handle to interact with it.
178
-
179
- :param cmd: Command to execute
180
- :param background: `False` if the command should be executed in the foreground, **`True` if the command should be executed in the background**
181
- :param envs: Environment variables used for the command
182
- :param user: User to run the command as
183
- :param cwd: Working directory to run the command
184
- :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
185
- :param request_timeout: Timeout for the request in **seconds**
186
-
187
- :return: `CommandHandle` handle to interact with the running command
188
- """
189
- ...
190
-
191
- def run(
192
- self,
193
- cmd: str,
194
- background: Union[bool, None] = None,
195
- envs: Optional[Dict[str, str]] = None,
196
- user: Username = "user",
197
- cwd: Optional[str] = None,
198
- on_stdout: Optional[Callable[[str], None]] = None,
199
- on_stderr: Optional[Callable[[str], None]] = None,
200
- timeout: Optional[float] = 60,
201
- request_timeout: Optional[float] = None,
202
- ):
203
- if background:
204
- cmd += " &"
205
- proc = self._start(
206
- cmd,
207
- envs,
208
- user,
209
- cwd,
210
- timeout,
211
- request_timeout,
212
- )
213
-
214
- return (
215
- proc
216
- if background
217
- else proc.wait(
218
- on_stdout=on_stdout,
219
- on_stderr=on_stderr,
220
- )
221
- )
222
-
223
- def _start(
224
- self,
225
- cmd: str,
226
- envs: Optional[Dict[str, str]] = None,
227
- user: Username = "user",
228
- cwd: Optional[str] = None,
229
- timeout: Optional[float] = 60,
230
- request_timeout: Optional[float] = None,
231
- ):
232
- events = self._rpc.start(
233
- api_pb2.StartRequest(
234
- process=api_pb2.ProcessConfig(
235
- cmd="/bin/bash",
236
- envs=envs,
237
- args=["-l","-c", cmd],
238
- cwd=cwd,
239
- ),
240
- ),
241
- self._headers,
242
- timeout_seconds=self._connection_config.get_request_timeout(
243
- request_timeout
244
- ),
245
- )
246
-
247
- try:
248
- start_event = events.__next__()
249
-
250
- if not start_event.HasField("event"):
251
- raise SandboxException(
252
- f"Failed to start process: expected start event, got {start_event}"
253
- )
254
-
255
- return CommandHandle(
256
- pid=start_event.event.start.pid,
257
- handle_kill=lambda: self.kill(start_event.event.start.pid),
258
- events=events,
259
- )
260
- except Exception as e:
261
- raise handle_rpc_exception(e)
262
-
263
- def connect(
264
- self,
265
- pid: int,
266
- timeout: Optional[float] = 60,
267
- request_timeout: Optional[float] = None,
268
- ):
269
- """
270
- Connects to a running command.
271
- You can use `CommandHandle.wait()` to wait for the command to finish and get execution results.
272
-
273
- :param pid: Process ID of the command to connect to. You can get the list of processes using `sandbox.commands.list()`
274
- :param timeout: Timeout for the connection in **seconds**. Using `0` will not limit the connection time
275
- :param request_timeout: Timeout for the request in **seconds**
276
-
277
- :return: `CommandHandle` handle to interact with the running command
278
- """
279
- events = self._rpc.connect(
280
- api_pb2.ConnectRequest(
281
- process=api_pb2.ProcessSelector(pid=pid),
282
- ),
283
- self._headers,
284
- timeout_seconds=self._connection_config.get_request_timeout(
285
- request_timeout
286
- ),
287
- )
288
- try:
289
- start_event = events.__next__()
290
- if not start_event.HasField("event"):
291
- raise SandboxException(
292
- f"Failed to connect to process: expected start event, got {start_event}"
293
- )
294
- return CommandHandle(
295
- pid=start_event.event.start.pid,
296
- handle_kill=lambda: self.kill(start_event.event.start.pid),
297
- events=events,
298
- )
299
- except Exception as e:
300
- raise handle_rpc_exception(e)
1
+ from typing import Callable, Dict, List, Literal, Optional, Union, overload
2
+
3
+ import httpcore
4
+ import urllib3
5
+
6
+ from ... import csx_connect
7
+ from ...connection_config import (
8
+ KEEPALIVE_PING_HEADER,
9
+ KEEPALIVE_PING_INTERVAL_SEC,
10
+ ConnectionConfig,
11
+ Username,
12
+ )
13
+ from ...exceptions import SandboxException
14
+ from ...generated import api_pb2, api_pb2_connect
15
+ from ...generated.rpc import authentication_header, handle_rpc_exception
16
+ from ...sandbox.commands.command_handle import CommandResult
17
+ from ...sandbox.commands.main import ProcessInfo
18
+ from ...sandbox_sync.commands.command_handle import CommandHandle
19
+
20
+
21
+ class Commands:
22
+ """
23
+ Module for executing commands in the sandbox.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ envd_api_url: str,
29
+ connection_config: ConnectionConfig,
30
+ pool: urllib3.PoolManager,
31
+ ) -> None:
32
+ self._connection_config = connection_config
33
+ self._rpc = api_pb2_connect.ProcessClient(
34
+ envd_api_url,
35
+ http_client=pool,
36
+ )
37
+ self._headers = connection_config.headers
38
+ self._pool = pool
39
+
40
+ def list(
41
+ self,
42
+ request_timeout: Optional[float] = None,
43
+ ) -> List[ProcessInfo]:
44
+ """
45
+ Lists all running commands and PTY sessions.
46
+
47
+ :param request_timeout: Timeout for the request in **seconds**
48
+
49
+ :return: List of running commands and PTY sessions
50
+ """
51
+ try:
52
+ res = self._rpc.list(
53
+ api_pb2.ListRequest(),
54
+ self._headers,
55
+ timeout_seconds=self._connection_config.get_request_timeout(
56
+ request_timeout
57
+ ),
58
+ )
59
+ return [
60
+ ProcessInfo(
61
+ pid=p.pid,
62
+ tag=p.tag,
63
+ cmd=p.config.cmd,
64
+ args=list(p.config.args),
65
+ envs=dict(p.config.envs),
66
+ cwd=p.config.cwd,
67
+ )
68
+ for p in res.processes
69
+ ]
70
+ except Exception as e:
71
+ raise handle_rpc_exception(e)
72
+
73
+ def kill(
74
+ self,
75
+ pid: int,
76
+ request_timeout: Optional[float] = None,
77
+ ) -> bool:
78
+ """
79
+ Kills a running command specified by its process ID.
80
+ It uses `SIGKILL` signal to kill the command.
81
+
82
+ :param pid: Process ID of the command. You can get the list of processes using `sandbox.commands.list()`
83
+ :param request_timeout: Timeout for the request in **seconds**
84
+
85
+ :return: `True` if the command was killed, `False` if the command was not found
86
+ """
87
+ try:
88
+ self._rpc.send_signal(
89
+ api_pb2.SendSignalRequest(
90
+ process=api_pb2.ProcessSelector(pid=pid),
91
+ signal=api_pb2.Signal.SIGNAL_SIGKILL,
92
+ ),
93
+ self._headers,
94
+ timeout_seconds=self._connection_config.get_request_timeout(
95
+ request_timeout
96
+ ),
97
+ )
98
+ return True
99
+ except Exception as e:
100
+ if "not found" in str(e):
101
+ return False
102
+ raise handle_rpc_exception(e)
103
+
104
+ def send_stdin(
105
+ self,
106
+ pid: int,
107
+ data: str,
108
+ request_timeout: Optional[float] = None,
109
+ ):
110
+ """
111
+ Send data to command stdin.
112
+
113
+ :param pid Process ID of the command. You can get the list of processes using `sandbox.commands.list()`.
114
+ :param data: Data to send to the command
115
+ :param request_timeout: Timeout for the request in **seconds**
116
+ """
117
+ try:
118
+ self._rpc.send_input(
119
+ api_pb2.SendInputRequest(
120
+ process=api_pb2.ProcessSelector(pid=pid),
121
+ input=api_pb2.ProcessInput(
122
+ stdin=data.encode(),
123
+ ),
124
+ ),
125
+ self._headers,
126
+ timeout_seconds=self._connection_config.get_request_timeout(
127
+ request_timeout
128
+ ),
129
+ )
130
+ except Exception as e:
131
+ raise handle_rpc_exception(e)
132
+
133
+ @overload
134
+ def run(
135
+ self,
136
+ cmd: str,
137
+ background: Union[Literal[False], None] = None,
138
+ envs: Optional[Dict[str, str]] = None,
139
+ user: Username = "user",
140
+ cwd: Optional[str] = None,
141
+ on_stdout: Optional[Callable[[str], None]] = None,
142
+ on_stderr: Optional[Callable[[str], None]] = None,
143
+ timeout: Optional[float] = 60,
144
+ request_timeout: Optional[float] = None,
145
+ ) -> CommandResult:
146
+ """
147
+ Start a new command and wait until it finishes executing.
148
+
149
+ :param cmd: Command to execute
150
+ :param background: **`False` if the command should be executed in the foreground**, `True` if the command should be executed in the background
151
+ :param envs: Environment variables used for the command
152
+ :param user: User to run the command as
153
+ :param cwd: Working directory to run the command
154
+ :param on_stdout: Callback for command stdout output
155
+ :param on_stderr: Callback for command stderr output
156
+ :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
157
+ :param request_timeout: Timeout for the request in **seconds**
158
+
159
+ :return: `CommandResult` result of the command execution
160
+ """
161
+ ...
162
+
163
+ @overload
164
+ def run(
165
+ self,
166
+ cmd: str,
167
+ background: Literal[True],
168
+ envs: Optional[Dict[str, str]] = None,
169
+ user: Username = "user",
170
+ cwd: Optional[str] = None,
171
+ on_stdout: None = None,
172
+ on_stderr: None = None,
173
+ timeout: Optional[float] = 60,
174
+ request_timeout: Optional[float] = None,
175
+ ) -> CommandHandle:
176
+ """
177
+ Start a new command and return a handle to interact with it.
178
+
179
+ :param cmd: Command to execute
180
+ :param background: `False` if the command should be executed in the foreground, **`True` if the command should be executed in the background**
181
+ :param envs: Environment variables used for the command
182
+ :param user: User to run the command as
183
+ :param cwd: Working directory to run the command
184
+ :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
185
+ :param request_timeout: Timeout for the request in **seconds**
186
+
187
+ :return: `CommandHandle` handle to interact with the running command
188
+ """
189
+ ...
190
+
191
+ def run(
192
+ self,
193
+ cmd: str,
194
+ background: Union[bool, None] = None,
195
+ envs: Optional[Dict[str, str]] = None,
196
+ user: Username = "user",
197
+ cwd: Optional[str] = None,
198
+ on_stdout: Optional[Callable[[str], None]] = None,
199
+ on_stderr: Optional[Callable[[str], None]] = None,
200
+ timeout: Optional[float] = 60,
201
+ request_timeout: Optional[float] = None,
202
+ ):
203
+ if background:
204
+ cmd += " &"
205
+ proc = self._start(
206
+ cmd,
207
+ envs,
208
+ user,
209
+ cwd,
210
+ timeout,
211
+ request_timeout,
212
+ )
213
+
214
+ return (
215
+ proc
216
+ if background
217
+ else proc.wait(
218
+ on_stdout=on_stdout,
219
+ on_stderr=on_stderr,
220
+ )
221
+ )
222
+
223
+ def _start(
224
+ self,
225
+ cmd: str,
226
+ envs: Optional[Dict[str, str]] = None,
227
+ user: Username = "user",
228
+ cwd: Optional[str] = None,
229
+ timeout: Optional[float] = 60,
230
+ request_timeout: Optional[float] = None,
231
+ ):
232
+ events = self._rpc.start(
233
+ api_pb2.StartRequest(
234
+ process=api_pb2.ProcessConfig(
235
+ cmd="/bin/bash",
236
+ envs=envs,
237
+ args=["-l","-c", cmd],
238
+ cwd=cwd,
239
+ ),
240
+ ),
241
+ self._headers,
242
+ timeout_seconds=self._connection_config.get_request_timeout(
243
+ request_timeout
244
+ ),
245
+ )
246
+
247
+ try:
248
+ start_event = events.__next__()
249
+
250
+ if not start_event.HasField("event"):
251
+ raise SandboxException(
252
+ f"Failed to start process: expected start event, got {start_event}"
253
+ )
254
+
255
+ return CommandHandle(
256
+ pid=start_event.event.start.pid,
257
+ handle_kill=lambda: self.kill(start_event.event.start.pid),
258
+ events=events,
259
+ )
260
+ except Exception as e:
261
+ raise handle_rpc_exception(e)
262
+
263
+ def connect(
264
+ self,
265
+ pid: int,
266
+ timeout: Optional[float] = 60,
267
+ request_timeout: Optional[float] = None,
268
+ ):
269
+ """
270
+ Connects to a running command.
271
+ You can use `CommandHandle.wait()` to wait for the command to finish and get execution results.
272
+
273
+ :param pid: Process ID of the command to connect to. You can get the list of processes using `sandbox.commands.list()`
274
+ :param timeout: Timeout for the connection in **seconds**. Using `0` will not limit the connection time
275
+ :param request_timeout: Timeout for the request in **seconds**
276
+
277
+ :return: `CommandHandle` handle to interact with the running command
278
+ """
279
+ events = self._rpc.connect(
280
+ api_pb2.ConnectRequest(
281
+ process=api_pb2.ProcessSelector(pid=pid),
282
+ ),
283
+ self._headers,
284
+ timeout_seconds=self._connection_config.get_request_timeout(
285
+ request_timeout
286
+ ),
287
+ )
288
+ try:
289
+ start_event = events.__next__()
290
+ if not start_event.HasField("event"):
291
+ raise SandboxException(
292
+ f"Failed to connect to process: expected start event, got {start_event}"
293
+ )
294
+ return CommandHandle(
295
+ pid=start_event.event.start.pid,
296
+ handle_kill=lambda: self.kill(start_event.event.start.pid),
297
+ events=events,
298
+ )
299
+ except Exception as e:
300
+ raise handle_rpc_exception(e)