Skip to content

Commit f84020a

Browse files
GatewayJjihongwei
andauthored
feat(sandbox): add async CSGHub sandbox HTTP client (#125)
* feat(sandbox): add async CSGHub sandbox HTTP client - Add pycsghub.sandbox_client (CsgHubSandbox, config, Pydantic models) - Add SandboxHttpError, SandboxTransportError, and SandboxResponseParseError - Declare httpx and pydantic>=2; add pytest to dev optional dependencies - Document sandbox usage in doc/sdk.md and doc/sdk_cn.md * feat(cli): add sandbox subcommands and SDK examples Add csghub-cli sandbox (create, get, start, stop, delete, exec, health) via pycsghub/cmd/sandbox.py and Typer. Document usage in doc/sdk.md and doc/sdk_cn.md. Default SandboxCreateRequest port to 0 so POST JSON sends 0 for server default. Add examples/sandbox_sdk.py and examples/sandbox_cli.py; extend sandbox tests. Made-with: Cursor * feat(cli): add sandbox upload command and harden sandbox flow Add sandbox upload CLI support and unify sandbox command error handling. Improve create option behavior/docs and add CLI integration tests to guard Typer regressions. Made-with: Cursor * chore: bump version to 0.9.0 for sandbox release Made-with: Cursor --------- Co-authored-by: jihongwei <wh.ji@opencsg.com>
1 parent 6944652 commit f84020a

17 files changed

Lines changed: 2430 additions & 3 deletions

File tree

doc/sdk.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,42 @@ This code:
165165
2. By generating batch classes dynamically and using class name reflection mechanism, a large number of classes with the same names as those automatically loaded by transformers are created in batches.
166166

167167
3. Assign it with the from_pretrained method, so the model read out will be an hf-transformers model.
168+
169+
### Sandbox (async HTTP client)
170+
171+
The SDK includes `pycsghub.sandbox_client` for CSGHub sandbox lifecycle and runtime APIs (async). Default `base_url` matches the public Hub (`https://hub.opencsg.com`, see `DEFAULT_CSGHUB_DOMAIN`). Set `CsgHubSandboxConfig` if you use a self-hosted Hub or a separate AI Gateway (`aigateway_url`; empty string means runtime calls use the same host as `base_url`).
172+
173+
Authentication uses the same token resolution as the rest of the SDK: optional `token=` on `CsgHubSandbox`, else `CSGHUB_TOKEN` / token file via `get_token_to_send`. HTTP failures raise `SandboxHttpError` or `SandboxTransportError`; `stream_execute_command` yields `ERROR: ...` lines on failure (it does not raise).
174+
175+
```python
176+
import asyncio
177+
from pycsghub.sandbox_client import CsgHubSandbox, SandboxCreateRequest
178+
179+
async def main() -> None:
180+
client = CsgHubSandbox(token="your_access_token")
181+
spec = SandboxCreateRequest(
182+
image="your-runner-image:tag",
183+
resource_id=77,
184+
sandbox_name="my-sandbox",
185+
)
186+
resp = await client.create_sandbox(spec)
187+
print(resp.spec.sandbox_name, resp.state.status)
188+
189+
asyncio.run(main())
190+
```
191+
192+
### Sandbox (CLI)
193+
194+
After installing the package, use the `csghub-cli sandbox` command group. Subcommands include `create`, `get`, `start`, `stop`, `delete` (same semantics as `stop`), `exec`, `upload`, and `health`. Shared options: `-e` / `--endpoint` (Hub `base_url`, default `https://hub.opencsg.com`), `--aigateway-url` for runtime routes when the gateway differs from the Hub, and `-k` / `--token` (optional; otherwise uses `CSGHUB_TOKEN` / token file like the rest of the SDK).
195+
196+
Examples:
197+
198+
```bash
199+
csghub-cli sandbox create -i your-runner-image:tag -n my-sandbox -k YOUR_TOKEN
200+
csghub-cli sandbox get my-sandbox -k YOUR_TOKEN
201+
csghub-cli sandbox exec my-sandbox "echo hello" -k YOUR_TOKEN
202+
csghub-cli sandbox upload my-sandbox ./local-file.txt -k YOUR_TOKEN
203+
csghub-cli sandbox health my-sandbox -k YOUR_TOKEN
204+
```
205+
206+
For a full `SandboxCreateRequest` body, pass `--spec path/to/spec.json` instead of `--image` / `--name` (`--spec` takes precedence and ignores `--image` / `--name`). Lifecycle commands print JSON; `exec` streams lines to stdout (exit code 1 if any line starts with `ERROR:`); `upload` prints the JSON response message; `health` prints `ok` on success.

doc/sdk_cn.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,42 @@ model = AutoModelForCausalLM.from_pretrained('model/repoid')
167167
2. 通过动态批量类生成与类名反射机制,批量创建大量与transformers自动类加载的重名类。
168168

169169
3. 为其赋予from_pretrained方法,这样读取出来的模型即为hf-transformers模型。
170+
171+
### 沙箱(异步 HTTP 客户端)
172+
173+
SDK 提供 `pycsghub.sandbox_client`,用于 CSGHub 沙箱生命周期与运行时接口(异步)。默认 `base_url` 与公网 Hub 一致(`https://hub.opencsg.com`,见 `DEFAULT_CSGHUB_DOMAIN`)。若使用自建 Hub 或独立 AI 网关,请配置 `CsgHubSandboxConfig``aigateway_url` 为空时,运行时请求与 `base_url` 同源。
174+
175+
鉴权与全库一致:可在 `CsgHubSandbox(token=...)` 传入 token,否则使用 `CSGHUB_TOKEN` 或 token 文件(`get_token_to_send`)。一般 HTTP 错误抛出 `SandboxHttpError``SandboxTransportError``stream_execute_command` 在失败时 **仅 yield** `ERROR: ...` 行,不抛异常。
176+
177+
```python
178+
import asyncio
179+
from pycsghub.sandbox_client import CsgHubSandbox, SandboxCreateRequest
180+
181+
async def main() -> None:
182+
client = CsgHubSandbox(token="your_access_token")
183+
spec = SandboxCreateRequest(
184+
image="your-runner-image:tag",
185+
resource_id=77,
186+
sandbox_name="my-sandbox",
187+
)
188+
resp = await client.create_sandbox(spec)
189+
print(resp.spec.sandbox_name, resp.state.status)
190+
191+
asyncio.run(main())
192+
```
193+
194+
### 沙箱(命令行)
195+
196+
安装本包后,可使用 `csghub-cli sandbox` 子命令组,包含 `create``get``start``stop``delete`(与 `stop` 语义相同)、`exec``upload``health`。公共参数:`-e` / `--endpoint`(Hub 的 `base_url`,默认 `https://hub.opencsg.com`)、`--aigateway-url`(运行时走独立 AI 网关时填写)、`-k` / `--token`(可选;不传则与全库一致使用 `CSGHUB_TOKEN` 或 token 文件)。
197+
198+
示例:
199+
200+
```bash
201+
csghub-cli sandbox create -i your-runner-image:tag -n my-sandbox -k YOUR_TOKEN
202+
csghub-cli sandbox get my-sandbox -k YOUR_TOKEN
203+
csghub-cli sandbox exec my-sandbox "echo hello" -k YOUR_TOKEN
204+
csghub-cli sandbox upload my-sandbox ./local-file.txt -k YOUR_TOKEN
205+
csghub-cli sandbox health my-sandbox -k YOUR_TOKEN
206+
```
207+
208+
若需完整 `SandboxCreateRequest` JSON,可使用 `--spec path/to/spec.json` 代替 `--image` / `--name``--spec` 优先,传入后会忽略 `--image` / `--name`)。生命周期类命令输出 JSON;`exec` 将流式输出逐行打印到标准输出(任一行以 `ERROR:` 开头则进程退出码为 1);`upload` 输出 JSON 响应消息;`health` 成功时打印 `ok`

examples/sandbox_cli.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Use csghub-cli to manage sandboxes (create, get, start, stop, exec, upload, health).
2+
3+
Install the package so the ``csghub-cli`` entry point is available::
4+
5+
pip install .
6+
7+
If ``csghub-cli`` is not on PATH, use::
8+
9+
python -m pycsghub.cli sandbox --help
10+
11+
This file documents typical commands. Set RUN_EXAMPLES = True to execute the
12+
sample ``create`` + ``get`` flow via subprocess (requires a valid token and image).
13+
"""
14+
15+
from __future__ import annotations
16+
17+
import os
18+
import subprocess
19+
import sys
20+
21+
# token = "your access token"
22+
token = None
23+
24+
endpoint = "https://hub.opencsg.com"
25+
26+
# Set True to run the subprocess example at the bottom (optional).
27+
RUN_EXAMPLES = False
28+
29+
# ---------------------------------------------------------------------------
30+
# Shell equivalents (run in terminal)
31+
# ---------------------------------------------------------------------------
32+
#
33+
# export CSGHUB_TOKEN="your access token"
34+
# export ENDPOINT="https://hub.opencsg.com"
35+
#
36+
# Create (image + name; port 0 means server default)
37+
#
38+
# csghub-cli sandbox create -i your-runner-image:tag -n my-sandbox \\
39+
# -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
40+
#
41+
# Full JSON body (volumes, many env vars): put SandboxCreateRequest JSON in spec.json
42+
#
43+
# csghub-cli sandbox create --spec /path/to/spec.json -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
44+
#
45+
# Query / lifecycle
46+
#
47+
# csghub-cli sandbox get my-sandbox -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
48+
# csghub-cli sandbox start my-sandbox -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
49+
# csghub-cli sandbox stop my-sandbox -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
50+
#
51+
# Runtime (if gateway differs from Hub, add --aigateway-url)
52+
#
53+
# csghub-cli sandbox exec my-sandbox "echo hello" -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
54+
# csghub-cli sandbox upload my-sandbox ./local-file.txt -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
55+
# csghub-cli sandbox health my-sandbox -e "$ENDPOINT" -k "$CSGHUB_TOKEN"
56+
#
57+
58+
59+
def _token_arg() -> list[str]:
60+
t = token or os.environ.get("CSGHUB_TOKEN")
61+
if not t:
62+
return []
63+
return ["-k", t]
64+
65+
66+
def run_subprocess_example() -> None:
67+
"""Minimal create + get using the same Python process (optional)."""
68+
base = [
69+
sys.executable,
70+
"-m",
71+
"pycsghub.cli",
72+
"sandbox",
73+
"-e",
74+
endpoint,
75+
]
76+
create = [
77+
*base,
78+
"create",
79+
"-i",
80+
"your-runner-image:tag",
81+
"-n",
82+
"my-sandbox",
83+
*_token_arg(),
84+
]
85+
get_cmd = [*base, "get", "my-sandbox", *_token_arg()]
86+
subprocess.run(create, check=True)
87+
subprocess.run(get_cmd, check=True)
88+
89+
90+
if __name__ == "__main__":
91+
if RUN_EXAMPLES:
92+
run_subprocess_example()
93+
else:
94+
print(__doc__)

examples/sandbox_sdk.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Use the CSGHub SDK (async) to manage sandboxes: create and query status.
2+
3+
Install: pip install . (from csghub-sdk repo root)
4+
Requires: httpx, pydantic (declared in pyproject.toml).
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import asyncio
10+
import logging
11+
12+
from pycsghub.constants import DEFAULT_CSGHUB_DOMAIN
13+
from pycsghub.sandbox_client import CsgHubSandbox, CsgHubSandboxConfig, SandboxCreateRequest
14+
15+
# token = "your access token"
16+
token = None
17+
18+
# Hub origin for lifecycle APIs (POST/GET /api/v1/sandboxes). Use your self-hosted URL if needed.
19+
endpoint = DEFAULT_CSGHUB_DOMAIN
20+
21+
# Optional: if sandbox runtime (exec/health) uses a different host than ``endpoint``, set it here.
22+
aigateway_url = ""
23+
24+
logging.basicConfig(
25+
level=getattr(logging, "INFO"),
26+
format="%(asctime)s - %(levelname)s - %(message)s",
27+
datefmt="%Y-%m-%d %H:%M:%S",
28+
handlers=[logging.StreamHandler()],
29+
)
30+
31+
32+
async def main() -> None:
33+
cfg = CsgHubSandboxConfig(
34+
base_url=endpoint,
35+
aigateway_url=aigateway_url,
36+
)
37+
client = CsgHubSandbox(csghub_sandbox_cfg=cfg, token=token)
38+
39+
spec = SandboxCreateRequest(
40+
image="your-runner-image:tag",
41+
sandbox_name="my-sandbox",
42+
resource_id=77,
43+
port=0,
44+
timeout=0,
45+
environments={"KEY": "value"},
46+
)
47+
48+
created = await client.create_sandbox(spec)
49+
print("created:", created.spec.sandbox_name, created.state.status)
50+
51+
sandbox_id = created.spec.sandbox_name
52+
current = await client.get_sandbox(sandbox_id)
53+
print("get:", current.spec.image, current.state.status)
54+
55+
56+
if __name__ == "__main__":
57+
asyncio.run(main())

0 commit comments

Comments
 (0)