SDK 第一个请求
多语言 SDK(talon-sandbox 系列)统一表层 — 装包、写 5 行、跑起来。本页演示 完整的"创建 → 跑命令 → 暴露端口 → 销毁"流程,Python / TypeScript / Go / Rust / C# 并列。
前置
- 一个能访问的 Talon Sandbox 实例。测试环境直接用官方托管地址
https://api.sandbox.talon.net.cn;本机 Docker 部署是http://localhost:18080(见 Docker quickstart / systemd quickstart) - 一个 API Key,形如
ask_...(login 后生成,或 bootstrap 时打印) - 网络:
network="allowlist"默认放 DNS + 配置好的白名单域;"open"全 开放出口;"sealed"完全隔离(只有 lo)
SDK 默认从环境变量读取服务地址与鉴权,先导出:
# 测试环境
export TALON_SANDBOX_SERVER="https://api.sandbox.talon.net.cn"
export TALON_SANDBOX_API_KEY="ask_xxxxxxxxxxxxxxxx"
TALON_SANDBOX_SERVER只填到域名,不要带/v1或/api—— SDK 内部自动拼/v1/*路径。不设这个变量时 SDK 默认连本机http://localhost:18080,这是接 入测试环境最常见的失败原因。
安装
pip install talon-sandboxnpm install talon-sandboxgo get github.com/talon-org/talon-sandbox-sdk-go# Cargo.toml
[dependencies]
talon-sandbox = { git = "https://github.com/talon-org/talon-sandbox-sdk-rust" }
tokio = { version = "1", features = ["full"] }dotnet add package TalonSandbox.Sdk# Homebrew(规划中)
brew install talon-sandbox
# 源码安装(立即可用)
go install github.com/talon-org/talon-sandbox-cli@latest
# 安装后两个名字都能用:talon-sandbox / tsb(短名)Hero 示例 — 拉起一个 dev server 并拿到 preview URL
import asyncio
import os
from talon_sandbox import Sandbox
async def main():
async with Sandbox.create(
image="talon-alpine",
resources={"cpu": 2, "memory": "4GiB"},
network="allowlist",
timeout="30m",
api_key=os.environ["TALON_SANDBOX_API_KEY"],
base_url=os.environ["TALON_SANDBOX_SERVER"],
) as sb:
await sb.fs.write_text("/workspace/app.py",
"print('hello from sandbox')\n")
result = await sb.run("python3 /workspace/app.py")
print(result.stdout) # → hello from sandbox
proc = await sb.spawn("python3 -m http.server 8000")
url = await sb.expose(8000)
print(f"Preview: {url}")
# → http://sb-xxx-8000.preview.example.com
# async with 退出时自动 kill,sandbox 被销毁
await asyncio.sleep(60)
asyncio.run(main())import { Sandbox } from "talon-sandbox";
const sb = await Sandbox.create({
image: "talon-alpine",
resources: { cpu: 2, memory: "4GiB" },
network: "allowlist",
timeout: "30m",
apiKey: process.env.TALON_SANDBOX_API_KEY!,
baseUrl: process.env.TALON_SANDBOX_SERVER!,
});
try {
await sb.fs.writeText("/workspace/app.js",
"console.log('hello from sandbox')\n");
const result = await sb.run("node /workspace/app.js");
console.log(result.stdout); // → hello from sandbox
const proc = await sb.spawn("npx http-server -p 8000");
const url = await sb.expose(8000);
console.log(`Preview: ${url}`);
// → http://sb-xxx-8000.preview.example.com
await new Promise(r => setTimeout(r, 60_000));
} finally {
await sb.kill();
}package main
import (
"context"
"fmt"
"log"
"os"
"time"
talonsandbox "github.com/talon-org/talon-sandbox-sdk-go"
)
func main() {
ctx := context.Background()
sb, err := talonsandbox.Create(ctx,
talonsandbox.WithImage("talon-alpine"),
talonsandbox.WithResources(talonsandbox.Resources{CPU: 2, Memory: "4GiB"}),
talonsandbox.WithNetwork("allowlist"),
talonsandbox.WithTimeout("30m"),
talonsandbox.WithAPIKey(os.Getenv("TALON_SANDBOX_API_KEY")),
talonsandbox.WithBaseURL(os.Getenv("TALON_SANDBOX_SERVER")),
)
if err != nil {
log.Fatal(err)
}
defer sb.Kill(ctx)
if err := sb.Files.WriteText(ctx, "/workspace/app.js",
"console.log('hello from sandbox')\n"); err != nil {
log.Fatal(err)
}
result, err := sb.Run(ctx, "node /workspace/app.js")
if err != nil {
log.Fatal(err)
}
fmt.Println(result.Stdout) // → hello from sandbox
_, err = sb.Spawn(ctx, "npx http-server -p 8000")
if err != nil {
log.Fatal(err)
}
exposed, err := sb.Expose(ctx, 8000)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Preview: %s\n", exposed.URL)
time.Sleep(60 * time.Second)
}// server / api_key 默认从 TALON_SANDBOX_SERVER / TALON_SANDBOX_API_KEY 读取
use talon_sandbox::{CreateOpts, ExposeOpts, Resources, Sandbox};
#[tokio::main]
async fn main() -> talon_sandbox::Result<()> {
let sb = Sandbox::create(CreateOpts {
image: Some("talon-alpine".into()),
resources: Resources {
cpu: 2.0,
memory: Some("4GiB".into()),
..Default::default()
},
network: Some("allowlist".into()),
timeout: Some("30m".into()),
..Default::default()
})
.await?;
sb.fs()
.write_text("/workspace/app.js", "console.log('hello from sandbox')\n")
.await?;
let result = sb.run("node /workspace/app.js").await?;
print!("{}", result.combined); // → hello from sandbox
sb.spawn("npx http-server -p 8000").await?;
let url = sb.expose(8000, ExposeOpts::default()).await?;
println!("Preview: {url}");
// → http://sb-xxx-8000.preview.example.com
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
sb.kill().await?; // 用完销毁,释放配额
Ok(())
}using TalonSandbox.Sdk;
await using var sb = await Sandbox.CreateAsync(new CreateOptions {
Image = "talon-alpine",
Resources = new Resources { Cpu = 2, Memory = "4GiB" },
Network = "allowlist",
Timeout = "30m",
ApiKey = Environment.GetEnvironmentVariable("TALON_SANDBOX_API_KEY"),
BaseUrl = Environment.GetEnvironmentVariable("TALON_SANDBOX_SERVER"),
});
await sb.Files.WriteTextAsync("/workspace/app.js",
"console.log('hello from sandbox')\n");
var result = await sb.RunAsync("node /workspace/app.js");
Console.WriteLine(result.Stdout); // → hello from sandbox
await sb.SpawnAsync("npx http-server -p 8000");
var url = await sb.ExposeAsync(8000);
Console.WriteLine($"Preview: {url.Url}");
await Task.Delay(TimeSpan.FromMinutes(1));
// await using 退出时自动 kill# 一行起跑 + 暴露:
SBX=$(tsb create \
--image talon-alpine \
--resources cpu=2,memory=4GiB \
--network allowlist \
--wait running \
-o id)
tsb run $SBX "python3 -c 'print(\"hello from sandbox\")'"
tsb spawn $SBX "npx http-server -p 8000"
tsb expose $SBX 8000
# → http://sb-xxx-8000.preview.example.com
# 收尾
tsb rm $SBX设计原则
四语言 SDK 表层一致,遵守同一份设计规范。
- 概念扁平 —
sb.run/sb.spawn/sb.expose/sb.fs/sb.terminal, 没有嵌套的sb.processes.create()这种 RPC 风格 - 字符串单位 —
memory="4GiB",timeout="30m",ttl="6h"(不是memory_bytes/idle_timeout_seconds) - async first-class — 所有 IO 都是 async/await
- 资源管理 —
async with/await using/defer sb.Kill()自动清理 - 命名抄 unix-docker-Pitcher —
run(同步,unix system())/spawn(异步, fork+exec)/kill(销毁)
常用操作速查
文件系统
await sb.fs.write_text("/workspace/main.py", "print('hi')")
text = await sb.fs.read_text("/workspace/main.py")
entries = await sb.fs.list("/workspace")
await sb.fs.remove("/workspace/old.py")
exists = await sb.fs.exists("/workspace/main.py")await sb.fs.writeText("/workspace/main.py", "print('hi')");
const text = await sb.fs.readText("/workspace/main.py");
const entries = await sb.fs.list("/workspace");
await sb.fs.remove("/workspace/old.py");
const exists = await sb.fs.exists("/workspace/main.py");sb.Files.WriteText(ctx, "/workspace/main.py", "print('hi')")
text, _ := sb.Files.ReadText(ctx, "/workspace/main.py")
entries, _ := sb.Files.List(ctx, "/workspace")
sb.Files.Remove(ctx, "/workspace/old.py")
exists, _ := sb.Files.Exists(ctx, "/workspace/main.py")sb.fs().write_text("/workspace/main.py", "print('hi')").await?;
let text = sb.fs().read_text("/workspace/main.py").await?;
let entries = sb.fs().list("/workspace").await?;
sb.fs().remove("/workspace/old.py").await?;await sb.Files.WriteTextAsync("/workspace/main.py", "print('hi')");
var text = await sb.Files.ReadTextAsync("/workspace/main.py");
var entries = await sb.Files.ListAsync("/workspace");
await sb.Files.RemoveAsync("/workspace/old.py");
var exists = await sb.Files.ExistsAsync("/workspace/main.py");echo "print('hi')" | tsb cp - $SBX:/workspace/main.py
tsb cp $SBX:/workspace/main.py ./main.py交互式终端(PTY)
async with sb.terminal.open(cmd="/bin/bash") as pty:
await pty.write("ls -la /workspace\n")
async for chunk in pty:
print(chunk, end="")const pty = await sb.terminal.open({ cmd: "/bin/bash" });
pty.on("data", chunk => process.stdout.write(chunk));
await pty.write("ls -la /workspace\n");
await pty.close();import "github.com/talon-org/talon-sandbox-sdk-go/terminal"
pty, _ := terminal.Open(ctx, sb, "/bin/bash")
defer pty.Close(ctx)
pty.Write(ctx, []byte("ls -la /workspace\n"))
// 读 pty.Output() channel 获取输出let mut pty = sb.terminal().open().await?;
pty.write(b"ls -la /workspace\n").await?;
while let Some(chunk) = pty.recv().await? {
print!("{}", String::from_utf8_lossy(&chunk));
}
pty.close().await?;await using var pty = await sb.Terminal.OpenAsync("/bin/bash");
pty.OnData += chunk => Console.Write(chunk);
await pty.WriteAsync("ls -la /workspace\n");tsb pty $SBX --cmd /bin/bash
# 当前终端进入 raw 模式,Ctrl-D 退出端口暴露 — 签名 URL(给第三方)
exposed = await sb.expose(8000, sign=True, ttl="1h")
print(exposed.url)
# → http://sb-xxx-8000.preview.example.com/?token=eyJ...const exposed = await sb.expose(8000, { sign: true, ttl: "1h" });
console.log(exposed.url);exposed, _ := sb.Expose(ctx, 8000,
talonsandbox.WithSign(true),
talonsandbox.WithTTL("1h"))
fmt.Println(exposed.URL)let url = sb.expose(8000, ExposeOpts {
sign: true,
ttl: Some("1h".into()),
..Default::default()
}).await?;
println!("{url}");var exposed = await sb.ExposeAsync(8000, new ExposeOptions {
Sign = true, Ttl = "1h"
});
Console.WriteLine(exposed.Url);tsb expose $SBX 8000 --sign --ttl 1h完整 expose 模型(显式 vs 动态、签名、自定义 subdomain)见 端口暴露。
暂停 / 恢复
await sb.pause() # 软暂停,进程冻结,workspace 保留
await sb.resume() # 毫秒级恢复await sb.pause();
await sb.resume();sb.Pause(ctx)
sb.Resume(ctx)sb.pause().await?; // 软暂停,进程冻结,workspace 保留
sb.resume().await?; // 毫秒级恢复await sb.PauseAsync();
await sb.ResumeAsync();tsb pause $SBX
tsb resume $SBX鉴权与配置
SDK 默认从环境变量读取服务地址与 API key:
| 环境变量 | 用途 |
|---|---|
TALON_SANDBOX_SERVER | API base URL。测试环境 https://api.sandbox.talon.net.cn;本机 http://localhost:18080 |
TALON_SANDBOX_API_KEY | API key,形如 ask_...。SDK 自动加 Bearer 前缀,原样填入即可 |
各语言 Sandbox.create() 的 api_key / base_url 参数会覆盖环境变量。Rust 还 可以用进程级 configure(不依赖环境变量):
use talon_sandbox::{configure, Config};
configure(
Config::new("https://api.sandbox.talon.net.cn")
.api_key("ask_xxxxxxxxxxxxxxxx"),
);
// 之后 Sandbox::create(...) 不传 client 即用这套默认CLI 还支持 ~/.config/talon-sandbox/config.yaml 多 context 配置:
tsb login --server https://api.sandbox.talon.net.cn
tsb whoami资源配额
创建参数受账号套餐限制,超限返回 422 quota exceeded。不传 resources / image 时由服务端兜底默认值,最省事。各套餐上限(单 sandbox + 并发数)以 [控制台 → 套餐]为准;不确定时先用默认值跑通,再按需调大。用完的 sandbox 务必 kill()(或 async with / defer / await using 自动清理),否则并发数占满会 阻塞后续创建。
偏好 raw HTTP?
如果你想直接拼 curl / fetch 看 wire 契约,跳到 curl 30 秒上手 — 那篇覆盖了同样的流程但全程 raw HTTP。
完整 OpenAPI 规格在 API 参考。
下一步
- Sandbox 生命周期 — 状态机
- 端口暴露 — explicit vs dynamic、签名 URL
- 签名 Preview URL — 安全模型
- API 参考 — REST 完整接口