Skip to content

Commit 42809e8

Browse files
authored
Merge pull request #41 from Gui-Yue/feat/opensandbox-support-main
feat: add OpenSandbox runtime support with docs and tests
2 parents dfc7005 + 7c252d2 commit 42809e8

25 files changed

Lines changed: 1797 additions & 34 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ tests/tmp/
3131
tests/.tmp/
3232
*.log
3333
*.txt
34-
.kode/
34+
.kode/

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- **Long-Running & Resumable** - Seven-stage checkpoints with Safe-Fork-Point for crash recovery
1111
- **Multi-Agent Collaboration** - AgentPool, Room messaging, and task delegation
1212
- **Enterprise Persistence** - SQLite/PostgreSQL support with unified WAL
13-
- **Cloud Sandbox** - [E2B](https://e2b.dev) integration for isolated remote code execution
13+
- **Cloud Sandbox** - [E2B](https://e2b.dev) and OpenSandbox integration for isolated remote code execution
1414
- **Extensible Ecosystem** - MCP tools, custom Providers, Skills system
1515

1616
## Quick Start
@@ -79,6 +79,15 @@ npm run example:getting-started # Minimal chat
7979
npm run example:agent-inbox # Event-driven inbox
8080
npm run example:approval # Tool approval workflow
8181
npm run example:room # Multi-agent collaboration
82+
npm run example:opensandbox # OpenSandbox basic usage
83+
```
84+
85+
OpenSandbox quick config:
86+
87+
```bash
88+
export OPEN_SANDBOX_API_KEY=... # optional (required only when auth is enabled)
89+
export OPEN_SANDBOX_ENDPOINT=http://127.0.0.1:8080 # optional
90+
export OPEN_SANDBOX_IMAGE=ubuntu # optional
8291
```
8392

8493
## Architecture for Scale
@@ -142,6 +151,8 @@ See [docs/en/guides/architecture.md](./docs/en/guides/architecture.md) for detai
142151
| **Guides** | |
143152
| [Events](./docs/en/guides/events.md) | Three-channel event system |
144153
| [Tools](./docs/en/guides/tools.md) | Built-in tools & custom tools |
154+
| [E2B Sandbox](./docs/en/guides/e2b-sandbox.md) | E2B cloud sandbox integration |
155+
| [OpenSandbox](./docs/en/guides/opensandbox-sandbox.md) | OpenSandbox self-hosted sandbox integration |
145156
| [Skills](./docs/en/guides/skills.md) | Skills system for reusable prompts |
146157
| [Providers](./docs/en/guides/providers.md) | Model provider configuration |
147158
| [Database](./docs/en/guides/database.md) | SQLite/PostgreSQL persistence |

README.zh-CN.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
- **长时运行与恢复** - 七段断点机制,支持 Safe-Fork-Point 崩溃恢复
1111
- **多 Agent 协作** - AgentPool、Room 消息、任务委派
1212
- **企业级持久化** - 支持 SQLite/PostgreSQL,统一 WAL 日志
13-
- **云端沙箱** - 集成 [E2B](https://e2b.dev),提供隔离的远程代码执行环境
13+
- **云端沙箱** - 集成 [E2B](https://e2b.dev) 与 OpenSandbox,提供隔离的远程代码执行环境
1414
- **可扩展生态** - MCP 工具、自定义 Provider、Skills 系统
1515

1616
## 快速开始
@@ -79,6 +79,15 @@ npm run example:getting-started # 最简对话
7979
npm run example:agent-inbox # 事件驱动收件箱
8080
npm run example:approval # 工具审批流程
8181
npm run example:room # 多Agent协作
82+
npm run example:opensandbox # OpenSandbox 基础使用
83+
```
84+
85+
OpenSandbox 快速配置:
86+
87+
```bash
88+
export OPEN_SANDBOX_API_KEY=... # 可选(仅在服务开启鉴权时需要)
89+
export OPEN_SANDBOX_ENDPOINT=http://127.0.0.1:8080 # 可选
90+
export OPEN_SANDBOX_IMAGE=ubuntu # 可选
8291
```
8392

8493
## 支持的 Provider
@@ -102,6 +111,8 @@ npm run example:room # 多Agent协作
102111
| **使用指南** | |
103112
| [事件系统](./docs/zh-CN/guides/events.md) | 三通道事件系统 |
104113
| [工具系统](./docs/zh-CN/guides/tools.md) | 内置工具与自定义工具 |
114+
| [E2B 沙箱](./docs/zh-CN/guides/e2b-sandbox.md) | E2B 云端沙箱接入 |
115+
| [OpenSandbox 沙箱](./docs/zh-CN/guides/opensandbox-sandbox.md) | OpenSandbox 自托管沙箱接入 |
105116
| [Skills 系统](./docs/zh-CN/guides/skills.md) | Skills 可复用提示词系统 |
106117
| [Provider 配置](./docs/zh-CN/guides/providers.md) | 模型 Provider 配置 |
107118
| [数据库存储](./docs/zh-CN/guides/database.md) | SQLite/PostgreSQL 持久化 |
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# OpenSandbox Guide
2+
3+
KODE SDK supports OpenSandbox as a sandbox backend for isolated command execution and file operations.
4+
5+
---
6+
7+
## Overview
8+
9+
| Feature | Description |
10+
|---------|-------------|
11+
| **Deployment** | Self-hosted OpenSandbox server |
12+
| **Runtime** | Container-based isolated execution environment |
13+
| **Lifecycle** | Create/connect/dispose by `sandboxId` |
14+
| **Compatibility** | Works with existing `bash_*` and `fs_*` tools |
15+
16+
### When to Use OpenSandbox vs E2B vs Local
17+
18+
| Scenario | Recommended |
19+
|----------|-------------|
20+
| You need self-hosted control in your own infra | OpenSandbox |
21+
| You want fully managed cloud sandbox | E2B |
22+
| Local development and offline debugging | Local Sandbox |
23+
24+
---
25+
26+
## Prerequisites
27+
28+
1. Docker daemon is running and can pull required images.
29+
2. OpenSandbox server is running (for example on `http://127.0.0.1:8080`).
30+
3. If your server enables auth, prepare an API key.
31+
32+
Optional environment variables:
33+
34+
```bash
35+
export OPEN_SANDBOX_API_KEY=... # optional, only when auth is enabled
36+
export OPEN_SANDBOX_ENDPOINT=http://127.0.0.1:8080 # optional
37+
export OPEN_SANDBOX_IMAGE=ubuntu # optional
38+
```
39+
40+
---
41+
42+
## Quick Start
43+
44+
### Create and Use a Sandbox
45+
46+
```typescript
47+
import { OpenSandbox } from '@shareai-lab/kode-sdk';
48+
49+
const sandbox = new OpenSandbox({
50+
kind: 'opensandbox',
51+
apiKey: process.env.OPEN_SANDBOX_API_KEY,
52+
endpoint: process.env.OPEN_SANDBOX_ENDPOINT,
53+
image: process.env.OPEN_SANDBOX_IMAGE || 'ubuntu',
54+
timeoutMs: 600_000,
55+
execTimeoutMs: 120_000,
56+
useServerProxy: false,
57+
watch: { mode: 'polling', pollIntervalMs: 1000 },
58+
lifecycle: { disposeAction: 'kill' },
59+
});
60+
61+
await sandbox.init();
62+
console.log('sandboxId:', sandbox.getSandboxId());
63+
64+
const result = await sandbox.exec('echo "hello opensandbox"');
65+
console.log(result.code, result.stdout.trim());
66+
67+
await sandbox.fs.write('demo.txt', 'hello from opensandbox');
68+
const content = await sandbox.fs.read('demo.txt');
69+
console.log(content.trim());
70+
71+
await sandbox.dispose();
72+
```
73+
74+
---
75+
76+
## Configuration
77+
78+
### OpenSandboxOptions
79+
80+
```typescript
81+
interface OpenSandboxOptions {
82+
kind: 'opensandbox';
83+
apiKey?: string;
84+
endpoint?: string;
85+
domain?: string;
86+
protocol?: 'http' | 'https';
87+
sandboxId?: string;
88+
image?: string;
89+
template?: string; // alias of image in current implementation
90+
workDir?: string; // default '/workspace'
91+
timeoutMs?: number;
92+
execTimeoutMs?: number;
93+
requestTimeoutSeconds?: number;
94+
useServerProxy?: boolean; // default false
95+
env?: Record<string, string>;
96+
metadata?: Record<string, string>;
97+
resource?: Record<string, string>;
98+
networkPolicy?: Record<string, any>;
99+
skipHealthCheck?: boolean;
100+
readyTimeoutSeconds?: number;
101+
healthCheckPollingInterval?: number;
102+
watch?: {
103+
mode?: 'native' | 'polling' | 'off'; // default 'polling'
104+
pollIntervalMs?: number; // default 1000
105+
};
106+
lifecycle?: {
107+
disposeAction?: 'close' | 'kill'; // default 'kill'
108+
};
109+
}
110+
```
111+
112+
### Environment Variables
113+
114+
| Variable | Description |
115+
|----------|-------------|
116+
| `OPEN_SANDBOX_API_KEY` | Optional API key (required only when server auth is enabled) |
117+
| `OPEN_SANDBOX_ENDPOINT` | OpenSandbox server endpoint |
118+
| `OPEN_SANDBOX_IMAGE` | Default image when creating a new sandbox |
119+
120+
---
121+
122+
## Agent Integration
123+
124+
### Use Sandbox Config (recommended)
125+
126+
```typescript
127+
const agent = await Agent.create({
128+
templateId: 'coder',
129+
sandbox: {
130+
kind: 'opensandbox',
131+
endpoint: process.env.OPEN_SANDBOX_ENDPOINT,
132+
apiKey: process.env.OPEN_SANDBOX_API_KEY,
133+
image: 'debian:latest',
134+
lifecycle: { disposeAction: 'kill' },
135+
},
136+
}, deps);
137+
```
138+
139+
When you pass sandbox config, `SandboxFactory.createAsync()` initializes OpenSandbox automatically.
140+
141+
### Use Sandbox Instance
142+
143+
```typescript
144+
const sandbox = new OpenSandbox({ kind: 'opensandbox', endpoint: 'http://127.0.0.1:8080' });
145+
await sandbox.init();
146+
147+
const agent = await Agent.create({ templateId: 'coder', sandbox }, deps);
148+
```
149+
150+
When you pass sandbox instance directly, call `sandbox.init()` yourself before `Agent.create()`.
151+
152+
### Resume with `sandboxId`
153+
154+
```typescript
155+
const sandbox = new OpenSandbox({ kind: 'opensandbox', endpoint: 'http://127.0.0.1:8080' });
156+
await sandbox.init();
157+
const id = sandbox.getSandboxId();
158+
159+
const restored = new OpenSandbox({
160+
kind: 'opensandbox',
161+
endpoint: 'http://127.0.0.1:8080',
162+
sandboxId: id,
163+
});
164+
await restored.init();
165+
```
166+
167+
---
168+
169+
## Watch and Lifecycle Semantics
170+
171+
1. `watch.mode='native'` uses `inotifywait` in the sandbox container.
172+
2. If `inotifywait` is unavailable or native stream exits unexpectedly, the SDK auto-falls back to polling mode.
173+
3. `watch.mode='off'` disables file watch registration.
174+
4. `disposeAction='kill'` performs `kill()` first, then `close()`.
175+
5. `disposeAction='close'` only closes the connection.
176+
6. Polling watch is level-triggered by mtime delta and may coalesce multiple writes within one polling interval.
177+
7. Polling watch does not guarantee one callback per write operation; treat events as "file changed" hints.
178+
179+
---
180+
181+
## Troubleshooting
182+
183+
1. `DOCKER::SANDBOX_IMAGE_PULL_FAILED` or `DOCKER::SANDBOX_EXECD_START_FAILED`: Docker cannot pull required images (`image` and `opensandbox/execd`).
184+
2. Verify OpenSandbox server endpoint is reachable from the SDK process.
185+
3. If you use a proxy, verify Docker daemon proxy and OpenSandbox server network settings separately.

docs/en/reference/api.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,39 @@ new E2BSandbox(options?: E2BSandboxOptions)
719719

720720
---
721721

722+
## OpenSandbox
723+
724+
Self-hosted sandbox powered by [OpenSandbox](https://www.npmjs.com/package/@alibaba-group/opensandbox) for isolated code execution.
725+
726+
### Constructor
727+
728+
```typescript
729+
new OpenSandbox(options: OpenSandboxOptions)
730+
```
731+
732+
### Methods
733+
734+
| Method | Signature | Description |
735+
|--------|-----------|-------------|
736+
| `init()` | `async init(): Promise<void>` | Initialize (create or connect) sandbox |
737+
| `exec(cmd, opts?)` | `async exec(cmd: string, opts?: { timeoutMs?: number }): Promise<SandboxExecResult>` | Execute a command |
738+
| `dispose()` | `async dispose(): Promise<void>` | Dispose sandbox by lifecycle policy |
739+
| `getSandboxId()` | `getSandboxId(): string \| undefined` | Get sandbox ID for persistence |
740+
| `isRunning()` | `async isRunning(): Promise<boolean>` | Check if sandbox is alive |
741+
| `watchFiles(paths, listener)` | `async watchFiles(...): Promise<string>` | Watch file changes (polling fallback supported) |
742+
| `unwatchFiles(id)` | `unwatchFiles(id: string): void` | Stop watching |
743+
| `getOpenSandbox()` | `getOpenSandbox(): OpenSandboxClient` | Access underlying OpenSandbox client |
744+
745+
### Properties
746+
747+
| Property | Type | Description |
748+
|----------|------|-------------|
749+
| `kind` | `'opensandbox'` | Sandbox type identifier |
750+
| `workDir` | `string` | Working directory path |
751+
| `fs` | `SandboxFS` | File system operations |
752+
753+
---
754+
722755
## E2BTemplateBuilder
723756

724757
Static utility for building custom E2B sandbox templates.
@@ -747,3 +780,5 @@ static async exists(alias: string, opts?: { apiKey?: string }): Promise<boolean>
747780
- [Types Reference](./types.md)
748781
- [Events Reference](./events-reference.md)
749782
- [Guides](../guides/events.md)
783+
- [E2B Sandbox Guide](../guides/e2b-sandbox.md)
784+
- [OpenSandbox Guide](../guides/opensandbox-sandbox.md)

docs/en/reference/types.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ interface SandboxConfig {
443443
### SandboxKind
444444

445445
```typescript
446-
type SandboxKind = 'local' | 'docker' | 'remote';
446+
type SandboxKind = 'local' | 'docker' | 'k8s' | 'remote' | 'vfs' | 'e2b' | 'opensandbox';
447447
```
448448

449449
---
@@ -516,6 +516,50 @@ interface E2BTemplateConfig {
516516

517517
---
518518

519+
## OpenSandbox Types
520+
521+
### OpenSandboxWatchMode
522+
523+
```typescript
524+
type OpenSandboxWatchMode = 'native' | 'polling' | 'off';
525+
```
526+
527+
### OpenSandboxOptions
528+
529+
```typescript
530+
interface OpenSandboxOptions {
531+
kind: 'opensandbox';
532+
apiKey?: string;
533+
endpoint?: string;
534+
domain?: string;
535+
protocol?: 'http' | 'https';
536+
sandboxId?: string;
537+
image?: string;
538+
template?: string;
539+
workDir?: string;
540+
timeoutMs?: number;
541+
execTimeoutMs?: number;
542+
requestTimeoutSeconds?: number;
543+
useServerProxy?: boolean;
544+
env?: Record<string, string>;
545+
metadata?: Record<string, string>;
546+
resource?: Record<string, string>;
547+
networkPolicy?: Record<string, any>;
548+
skipHealthCheck?: boolean;
549+
readyTimeoutSeconds?: number;
550+
healthCheckPollingInterval?: number;
551+
watch?: {
552+
mode?: OpenSandboxWatchMode;
553+
pollIntervalMs?: number;
554+
};
555+
lifecycle?: {
556+
disposeAction?: 'close' | 'kill';
557+
};
558+
}
559+
```
560+
561+
---
562+
519563
## References
520564

521565
- [API Reference](./api.md)

0 commit comments

Comments
 (0)