akernel-sdk 0.1.0__tar.gz
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.
- akernel_sdk-0.1.0/PKG-INFO +390 -0
- akernel_sdk-0.1.0/README.md +359 -0
- akernel_sdk-0.1.0/akernel_sdk/__init__.py +71 -0
- akernel_sdk-0.1.0/akernel_sdk/_addresses.py +166 -0
- akernel_sdk-0.1.0/akernel_sdk/_instance.py +416 -0
- akernel_sdk-0.1.0/akernel_sdk/_openyuanrong.py +320 -0
- akernel_sdk-0.1.0/akernel_sdk/_pty_transport.py +304 -0
- akernel_sdk-0.1.0/akernel_sdk/cli.py +549 -0
- akernel_sdk-0.1.0/akernel_sdk/commands.py +207 -0
- akernel_sdk-0.1.0/akernel_sdk/filesystem.py +291 -0
- akernel_sdk-0.1.0/akernel_sdk/pty.py +250 -0
- akernel_sdk-0.1.0/akernel_sdk/py.typed +0 -0
- akernel_sdk-0.1.0/akernel_sdk/sandbox.py +354 -0
- akernel_sdk-0.1.0/akernel_sdk/types.py +204 -0
- akernel_sdk-0.1.0/akernel_sdk.egg-info/PKG-INFO +390 -0
- akernel_sdk-0.1.0/akernel_sdk.egg-info/SOURCES.txt +20 -0
- akernel_sdk-0.1.0/akernel_sdk.egg-info/dependency_links.txt +1 -0
- akernel_sdk-0.1.0/akernel_sdk.egg-info/entry_points.txt +2 -0
- akernel_sdk-0.1.0/akernel_sdk.egg-info/requires.txt +7 -0
- akernel_sdk-0.1.0/akernel_sdk.egg-info/top_level.txt +1 -0
- akernel_sdk-0.1.0/pyproject.toml +87 -0
- akernel_sdk-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akernel-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK and CLI for AKernel remote sandboxes
|
|
5
|
+
Author: AKernel Authors
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/akernel-dev/akernel
|
|
8
|
+
Project-URL: Documentation, https://github.com/akernel-dev/akernel/tree/main/sdk/python
|
|
9
|
+
Project-URL: Repository, https://github.com/akernel-dev/akernel.git
|
|
10
|
+
Project-URL: Issues, https://github.com/akernel-dev/akernel/issues
|
|
11
|
+
Keywords: ai-agents,sandbox,gvisor,remote-execution
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: openyuanrong-sdk==0.7.51
|
|
26
|
+
Requires-Dist: websockets>=10.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build<2,>=1.2; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy<2,>=1.10; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff<1,>=0.11; extra == "dev"
|
|
31
|
+
|
|
32
|
+
# AKernel Python SDK
|
|
33
|
+
|
|
34
|
+
`akernel-sdk` is the Python interface for creating and managing remote AKernel sandboxes. Applications use one stable API for commands, files, interactive PTYs, port forwarding, and reverse tunnels. The current implementation uses `openYuanrong` as its backend adapter; backend-specific handles and namespaces are not part of the public API.
|
|
35
|
+
|
|
36
|
+
## Navigation
|
|
37
|
+
|
|
38
|
+
- [Install and configure](#install-and-configure)
|
|
39
|
+
- [Create a sandbox](#create-a-sandbox)
|
|
40
|
+
- [Commands](#commands)
|
|
41
|
+
- [Filesystem](#filesystem)
|
|
42
|
+
- [Interactive PTYs](#interactive-ptys)
|
|
43
|
+
- [Port forwarding](#port-forwarding)
|
|
44
|
+
- [Reverse tunnels](#reverse-tunnels)
|
|
45
|
+
- [Rootfs and mounts](#rootfs-and-mounts)
|
|
46
|
+
- [Resources and lifecycle](#resources-and-lifecycle)
|
|
47
|
+
- [CLI](#cli)
|
|
48
|
+
- [Examples and tests](#examples-and-tests)
|
|
49
|
+
|
|
50
|
+
## Install and configure
|
|
51
|
+
|
|
52
|
+
AKernel SDK 0.1.0 requires Python 3.10 or newer.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install akernel-sdk
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To install from a source checkout:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python -m pip install ./sdk/python
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Configure the public AKernel entrypoint and a signed JWT token:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
export AKERNEL_SERVER_ADDRESS="akernel.example.com"
|
|
68
|
+
export AKERNEL_TOKEN="<token>"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Address behavior is deterministic:
|
|
72
|
+
|
|
73
|
+
- A host or IP without a port uses HTTPS/WSS on 443 for the frontend and HTTP
|
|
74
|
+
on 80 for public sandbox port URLs.
|
|
75
|
+
- `host:port` uses that port as a shared HTTPS/WSS endpoint.
|
|
76
|
+
- `AKERNEL_GATEWAY_ADDRESS` overrides the port-forwarding and exec gateway for
|
|
77
|
+
standalone or custom topologies. An override without a scheme uses HTTP/WS.
|
|
78
|
+
|
|
79
|
+
The standalone launcher prints the Traefik container IP to use as
|
|
80
|
+
`AKERNEL_SERVER_ADDRESS`.
|
|
81
|
+
|
|
82
|
+
## Create a sandbox
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from akernel_sdk import Sandbox
|
|
86
|
+
|
|
87
|
+
with Sandbox(cpu=1000, memory=2048) as sandbox:
|
|
88
|
+
result = sandbox.commands.run("printf hello")
|
|
89
|
+
print(result.stdout)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The constructor accepts:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
Sandbox(
|
|
96
|
+
image: str | None = None,
|
|
97
|
+
rootfs: S3Config | None = None,
|
|
98
|
+
runtime: str = "runsc",
|
|
99
|
+
cpu: int = 1000,
|
|
100
|
+
memory: int = 4096,
|
|
101
|
+
cpu_limit: int = 0,
|
|
102
|
+
mem_limit: int = 0,
|
|
103
|
+
idle_timeout: int = 300,
|
|
104
|
+
schedule_timeout: int = 30,
|
|
105
|
+
env: dict[str, str] | None = None,
|
|
106
|
+
name: str | None = None,
|
|
107
|
+
cwd: str | None = None,
|
|
108
|
+
port_forwardings: list[int] | None = None,
|
|
109
|
+
mounts: list[Mount] | None = None,
|
|
110
|
+
reverse_tunnel: HttpReverseTunnel | None = None,
|
|
111
|
+
detached: bool = False,
|
|
112
|
+
node_id: str | None = None,
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`cpu` is measured in millicores and `memory` in MiB. A zero CPU or memory
|
|
117
|
+
limit means the limit follows the corresponding request. A positive limit
|
|
118
|
+
must not be smaller than its request.
|
|
119
|
+
|
|
120
|
+
AKernel 0.1.0 supports the gVisor `runsc` runtime. The independent `runtime`
|
|
121
|
+
parameter leaves room for future Kata support without changing the rootfs API.
|
|
122
|
+
|
|
123
|
+
## Commands
|
|
124
|
+
|
|
125
|
+
Run a foreground command:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
result = sandbox.commands.run(
|
|
129
|
+
"printf $GREETING",
|
|
130
|
+
envs={"GREETING": "hello"},
|
|
131
|
+
cwd="/tmp",
|
|
132
|
+
timeout=60,
|
|
133
|
+
)
|
|
134
|
+
print(result.stdout, result.stderr, result.exit_code)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Run and control a background command:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
handle = sandbox.commands.run("sleep 30", background=True)
|
|
141
|
+
print(handle.pid)
|
|
142
|
+
|
|
143
|
+
for process in sandbox.commands.list():
|
|
144
|
+
print(process.pid, process.command, process.running)
|
|
145
|
+
|
|
146
|
+
handle.kill()
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Enable stdin only when it is needed:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
handle = sandbox.commands.run("wc -l", background=True, stdin=True)
|
|
153
|
+
handle.send_stdin("one\ntwo\n")
|
|
154
|
+
handle.close_stdin()
|
|
155
|
+
result = handle.wait(timeout=15)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Foreground commands use one actor RPC with the configured timeout. Background commands return a handle whose `wait()` method also performs one actor RPC.
|
|
159
|
+
|
|
160
|
+
## Filesystem
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
sandbox.files.write("/tmp/message.txt", "hello")
|
|
164
|
+
print(sandbox.files.read("/tmp/message.txt"))
|
|
165
|
+
|
|
166
|
+
sandbox.files.write("/tmp/data.bin", b"\x00\x01")
|
|
167
|
+
print(sandbox.files.read("/tmp/data.bin", format="bytes"))
|
|
168
|
+
|
|
169
|
+
for entry in sandbox.files.list("/tmp"):
|
|
170
|
+
print(entry.path, entry.type, entry.size)
|
|
171
|
+
|
|
172
|
+
sandbox.files.make_dir("/workspace")
|
|
173
|
+
sandbox.files.rename("/tmp/message.txt", "/workspace/message.txt")
|
|
174
|
+
sandbox.files.remove("/workspace/message.txt")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Copy local files or directories through the frontend exec WebSocket:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
sandbox.files.copy_from_local("./project", "/workspace/project")
|
|
181
|
+
sandbox.files.copy_to_local("/workspace/result.json", "./result.json")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Interactive PTYs
|
|
185
|
+
|
|
186
|
+
Use `sandbox.pty` for an interactive byte stream with stdin, streaming output, terminal resizing, and an exit status:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
import sys
|
|
190
|
+
|
|
191
|
+
from akernel_sdk import Sandbox
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def write_output(data: bytes) -> None:
|
|
195
|
+
sys.stdout.buffer.write(data)
|
|
196
|
+
sys.stdout.buffer.flush()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
with Sandbox() as sandbox:
|
|
200
|
+
with sandbox.pty.create(on_data=write_output) as session:
|
|
201
|
+
session.send_stdin(b"echo hello from PTY\n")
|
|
202
|
+
session.resize(rows=40, cols=120)
|
|
203
|
+
session.send_stdin(b"exit 7\n")
|
|
204
|
+
print(session.wait())
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
PTY output remains bytes so the SDK does not guess the terminal encoding. Use `session.close_stdin()` to signal end-of-input while continuing to receive output. A session belongs to its WebSocket connection: closing it terminates the remote interactive process, and reconnecting to an existing session is not supported.
|
|
208
|
+
|
|
209
|
+
Use `sandbox.commands` instead when the caller needs separate stdout and stderr, a complete `CommandResult`, or a controllable background process. The former `Shell` API and its actor `bash_*` methods were removed before the v0.1.0 public API was released.
|
|
210
|
+
|
|
211
|
+
## Port forwarding
|
|
212
|
+
|
|
213
|
+
Declare each sandbox port at creation time:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from akernel_sdk import Sandbox
|
|
217
|
+
|
|
218
|
+
with Sandbox(port_forwardings=[8080]) as sandbox:
|
|
219
|
+
server = sandbox.commands.run(
|
|
220
|
+
"python3 -m http.server 8080 --bind 0.0.0.0",
|
|
221
|
+
background=True,
|
|
222
|
+
)
|
|
223
|
+
print(sandbox.get_port_url(8080))
|
|
224
|
+
server.kill()
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`get_port_url()` rejects undeclared ports. Pass `internal=True` only when a
|
|
228
|
+
deployment operator explicitly wants the direct Traefik address instead of the
|
|
229
|
+
public gateway.
|
|
230
|
+
|
|
231
|
+
## Reverse tunnels
|
|
232
|
+
|
|
233
|
+
A reverse tunnel lets sandbox code call an HTTP or HTTPS service reachable
|
|
234
|
+
from the machine running the SDK:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from akernel_sdk import HttpReverseTunnel, Sandbox
|
|
238
|
+
|
|
239
|
+
tunnel = HttpReverseTunnel(
|
|
240
|
+
target="https://service.example.com",
|
|
241
|
+
reverse_port=8765,
|
|
242
|
+
listen_port=8766,
|
|
243
|
+
connect_timeout=60,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
with Sandbox(reverse_tunnel=tunnel) as sandbox:
|
|
247
|
+
result = sandbox.commands.run(
|
|
248
|
+
f"curl {sandbox.reverse_tunnel.url}/health"
|
|
249
|
+
)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`reverse_port` carries the WebSocket tunnel through Traefik. `listen_port` is
|
|
253
|
+
the loopback HTTP listener used inside the sandbox. Consequently,
|
|
254
|
+
`sandbox.reverse_tunnel.url` is always
|
|
255
|
+
`http://127.0.0.1:<listen_port>`, even when `target` uses HTTPS.
|
|
256
|
+
|
|
257
|
+
For an HTTPS target, the SDK-side tunnel client performs the TLS handshake and
|
|
258
|
+
certificate verification. The sandbox application talks only to its loopback
|
|
259
|
+
HTTP listener. AKernel 0.1.0 supports one HTTP/WebSocket reverse tunnel per
|
|
260
|
+
sandbox; it does not expose a general TCP tunnel.
|
|
261
|
+
|
|
262
|
+
## Rootfs and mounts
|
|
263
|
+
|
|
264
|
+
Use a public OCI image:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
with Sandbox(image="ubuntu:24.04") as sandbox:
|
|
268
|
+
print(sandbox.commands.run("cat /etc/os-release").stdout)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Or use an object in S3-compatible storage as the rootfs:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from akernel_sdk import S3Config, Sandbox
|
|
275
|
+
|
|
276
|
+
rootfs = S3Config(
|
|
277
|
+
endpoint="https://s3.example.com",
|
|
278
|
+
bucket="akernel-rootfs",
|
|
279
|
+
object="ubuntu-24.04/rootfs.img",
|
|
280
|
+
access_key="<optional>",
|
|
281
|
+
secret_key="<optional>",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
with Sandbox(rootfs=rootfs) as sandbox:
|
|
285
|
+
print(sandbox.commands.run("cat /etc/os-release").stdout)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`image` and `rootfs` are mutually exclusive. The SDK generates the backend
|
|
289
|
+
wire representation; callers do not pass raw rootfs JSON or override the
|
|
290
|
+
runtime inside an S3 object.
|
|
291
|
+
|
|
292
|
+
The same `S3Config` type can be used as a read-only mount source:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from akernel_sdk import Mount
|
|
296
|
+
|
|
297
|
+
mount = Mount(target="/models", type="erofs", s3_config=rootfs)
|
|
298
|
+
with Sandbox(mounts=[mount]) as sandbox:
|
|
299
|
+
print(sandbox.commands.run("ls /models").stdout)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
OCI images can also be mounted read-only:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
mount = Mount(target="/opt/tools", image_url="ubuntu:24.04")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Resources and lifecycle
|
|
309
|
+
|
|
310
|
+
`resources()` returns stable `NodeInfo` values rather than backend objects:
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
from akernel_sdk import resources
|
|
314
|
+
|
|
315
|
+
for node in resources():
|
|
316
|
+
print(node.id, node.status, node.capacity, node.allocatable, node.labels)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Use the context manager for ordinary sandboxes. For a named detached sandbox,
|
|
320
|
+
explicitly delete it when it is no longer needed:
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
sandbox = Sandbox(name="worker", detached=True)
|
|
324
|
+
sandbox.kill() # closes local clients; remote sandbox remains
|
|
325
|
+
Sandbox.delete("worker") # terminates the named remote sandbox
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`sandbox.id` is the physical ID shown by `ak list`. `get_info()` returns a
|
|
329
|
+
`SandboxInfo` containing `id`, state, requested CPU and memory, and the OCI
|
|
330
|
+
image when one was configured.
|
|
331
|
+
|
|
332
|
+
## CLI
|
|
333
|
+
|
|
334
|
+
The `ak` CLI is installed with the SDK package:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
ak resources
|
|
338
|
+
ak list
|
|
339
|
+
ak list --quiet
|
|
340
|
+
ak exec <sandbox-id>
|
|
341
|
+
ak exec <sandbox-id> -- /bin/sh
|
|
342
|
+
ak delete <sandbox-id> [<sandbox-id> ...]
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
It uses the same `AKERNEL_SERVER_ADDRESS` and `AKERNEL_TOKEN` environment as
|
|
346
|
+
the Python API.
|
|
347
|
+
|
|
348
|
+
## Examples and tests
|
|
349
|
+
|
|
350
|
+
Maintained examples are under [`examples/`](./examples):
|
|
351
|
+
|
|
352
|
+
- `basic_usage.py`
|
|
353
|
+
- `command_stdin.py`
|
|
354
|
+
- `custom_image.py`
|
|
355
|
+
- `named_sandbox.py`
|
|
356
|
+
- `pty.py`
|
|
357
|
+
- `port_forwarding.py`
|
|
358
|
+
- `reverse_tunnel.py`
|
|
359
|
+
- `s3_rootfs_and_mounts.py`
|
|
360
|
+
|
|
361
|
+
Run unit tests without a deployment:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
PYTHONPATH=sdk/python \
|
|
365
|
+
python -m unittest discover -s sdk/python/tests/unit -t sdk/python -v
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Run the integration suite against a configured deployment:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
export AKERNEL_RUN_INTEGRATION=1
|
|
372
|
+
PYTHONPATH=sdk/python \
|
|
373
|
+
python -m unittest discover -s sdk/python/tests/integration -t sdk/python -v
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Load and transfer benchmarks live under [`benchmarks/`](./benchmarks) and are
|
|
377
|
+
not part of the default test suite.
|
|
378
|
+
|
|
379
|
+
## Public value types
|
|
380
|
+
|
|
381
|
+
| Type | Fields |
|
|
382
|
+
|---|---|
|
|
383
|
+
| `CommandResult` | `stdout`, `stderr`, `exit_code` |
|
|
384
|
+
| `CommandInfo` | `pid`, `command`, `running` |
|
|
385
|
+
| `EntryInfo` | `name`, `path`, `type`, `size`, `permissions`, `modified_time` |
|
|
386
|
+
| `SandboxInfo` | `id`, `state`, `cpu`, `memory`, `image` |
|
|
387
|
+
| `NodeInfo` | `id`, `status`, `capacity`, `allocatable`, `labels` |
|
|
388
|
+
| `S3Config` | `endpoint`, `bucket`, `object`, optional credentials |
|
|
389
|
+
| `Mount` | `target`, one source, and `type` |
|
|
390
|
+
| `HttpReverseTunnel` | `target`, `reverse_port`, `listen_port`, `connect_timeout` |
|