@shareai-lab/kode-sdk 2.7.3 → 2.7.4

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.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  - **Long-Running & Resumable** - Seven-stage checkpoints with Safe-Fork-Point for crash recovery
11
11
  - **Multi-Agent Collaboration** - AgentPool, Room messaging, and task delegation
12
12
  - **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
14
14
  - **Extensible Ecosystem** - MCP tools, custom Providers, Skills system
15
15
 
16
16
  ## Quick Start
@@ -79,6 +79,15 @@ npm run example:getting-started # Minimal chat
79
79
  npm run example:agent-inbox # Event-driven inbox
80
80
  npm run example:approval # Tool approval workflow
81
81
  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
82
91
  ```
83
92
 
84
93
  ## Architecture for Scale
@@ -142,6 +151,8 @@ See [docs/en/guides/architecture.md](./docs/en/guides/architecture.md) for detai
142
151
  | **Guides** | |
143
152
  | [Events](./docs/en/guides/events.md) | Three-channel event system |
144
153
  | [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 |
145
156
  | [Skills](./docs/en/guides/skills.md) | Skills system for reusable prompts |
146
157
  | [Providers](./docs/en/guides/providers.md) | Model provider configuration |
147
158
  | [Database](./docs/en/guides/database.md) | SQLite/PostgreSQL persistence |
package/README.zh-CN.md CHANGED
@@ -10,7 +10,7 @@
10
10
  - **长时运行与恢复** - 七段断点机制,支持 Safe-Fork-Point 崩溃恢复
11
11
  - **多 Agent 协作** - AgentPool、Room 消息、任务委派
12
12
  - **企业级持久化** - 支持 SQLite/PostgreSQL,统一 WAL 日志
13
- - **云端沙箱** - 集成 [E2B](https://e2b.dev),提供隔离的远程代码执行环境
13
+ - **云端沙箱** - 集成 [E2B](https://e2b.dev) 与 OpenSandbox,提供隔离的远程代码执行环境
14
14
  - **可扩展生态** - MCP 工具、自定义 Provider、Skills 系统
15
15
 
16
16
  ## 快速开始
@@ -79,6 +79,15 @@ npm run example:getting-started # 最简对话
79
79
  npm run example:agent-inbox # 事件驱动收件箱
80
80
  npm run example:approval # 工具审批流程
81
81
  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 # 可选
82
91
  ```
83
92
 
84
93
  ## 支持的 Provider
@@ -102,6 +111,8 @@ npm run example:room # 多Agent协作
102
111
  | **使用指南** | |
103
112
  | [事件系统](./docs/zh-CN/guides/events.md) | 三通道事件系统 |
104
113
  | [工具系统](./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 自托管沙箱接入 |
105
116
  | [Skills 系统](./docs/zh-CN/guides/skills.md) | Skills 可复用提示词系统 |
106
117
  | [Provider 配置](./docs/zh-CN/guides/providers.md) | 模型 Provider 配置 |
107
118
  | [数据库存储](./docs/zh-CN/guides/database.md) | SQLite/PostgreSQL 持久化 |
@@ -195,12 +195,22 @@ class Agent {
195
195
  config.agentId = Agent.generateAgentId();
196
196
  }
197
197
  const template = deps.templateRegistry.get(config.templateId);
198
- const sandboxConfig = config.sandbox && 'kind' in config.sandbox
198
+ const sandboxConfigSource = config.sandbox && 'kind' in config.sandbox
199
199
  ? config.sandbox
200
200
  : template.sandbox;
201
+ const sandboxConfig = sandboxConfigSource
202
+ ? { ...sandboxConfigSource }
203
+ : undefined;
201
204
  const sandbox = typeof config.sandbox === 'object' && 'exec' in config.sandbox
202
205
  ? config.sandbox
203
206
  : await deps.sandboxFactory.createAsync(sandboxConfig || { kind: 'local', workDir: process.cwd() });
207
+ // OpenSandbox creates sandbox id at runtime; persist it for strict resume.
208
+ if (sandboxConfig?.kind === 'opensandbox' && typeof sandbox.getSandboxId === 'function') {
209
+ const sandboxId = sandbox.getSandboxId();
210
+ if (sandboxId) {
211
+ sandboxConfig.sandboxId = sandboxId;
212
+ }
213
+ }
204
214
  const model = config.model
205
215
  ? config.model
206
216
  : config.modelConfig
package/dist/index.d.ts CHANGED
@@ -27,6 +27,8 @@ export { PostgresStore } from './infra/db/postgres/postgres-store';
27
27
  export { Sandbox, LocalSandbox, SandboxKind } from './infra/sandbox';
28
28
  export { E2BSandbox, E2BFS, E2BTemplateBuilder } from './infra/e2b';
29
29
  export type { E2BSandboxOptions, E2BTemplateConfig } from './infra/e2b';
30
+ export { OpenSandbox, OpenSandboxFS } from './infra/opensandbox';
31
+ export type { OpenSandboxOptions, OpenSandboxWatchMode } from './infra/opensandbox';
30
32
  export { ModelProvider, ModelConfig, ModelResponse, ModelStreamChunk, AnthropicProvider, OpenAIProvider, GeminiProvider, } from './infra/provider';
31
33
  export { SandboxFactory } from './infra/sandbox-factory';
32
34
  export { FsRead } from './tools/fs_read';
package/dist/index.js CHANGED
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.BashLogs = exports.BashRun = exports.FsMultiEdit = exports.FsGrep = exports.FsGlob = exports.FsEdit = exports.FsWrite = exports.FsRead = exports.SandboxFactory = exports.GeminiProvider = exports.OpenAIProvider = exports.AnthropicProvider = exports.E2BTemplateBuilder = exports.E2BFS = exports.E2BSandbox = exports.LocalSandbox = exports.PostgresStore = exports.SqliteStore = exports.createExtendedStore = exports.createStore = exports.JSONStore = exports.ProviderCapabilityError = exports.UnsupportedProviderError = exports.UnsupportedContentBlockError = exports.MultimodalValidationError = exports.ResumeError = exports.PermissionModeRegistry = exports.permissionModes = exports.ToolRunner = exports.TodoManager = exports.MessageQueue = exports.PermissionManager = exports.BreakpointManager = exports.SandboxFileManager = exports.OperationStatus = exports.OperationType = exports.OperationQueue = exports.SkillsManagementManager = exports.SkillsManager = exports.TimeBridge = exports.TodoService = exports.AgentTemplateRegistry = exports.FilePool = exports.ContextManager = exports.HookManager = exports.EventBus = exports.Scheduler = exports.Room = exports.AgentPool = exports.Agent = void 0;
18
- exports.generateAgentId = exports.extendSchema = exports.mergeSchemas = exports.SchemaBuilder = exports.patterns = exports.schema = exports.inferFromExample = exports.createScriptsTool = exports.createSkillsTool = exports.toolMethod = exports.ToolKit = exports.disconnectAllMCP = exports.disconnectMCP = exports.getMCPTools = exports.tools = exports.tool = exports.extractTools = exports.defineTools = exports.defineTool = exports.globalToolRegistry = exports.ToolRegistry = exports.builtin = exports.TodoWrite = exports.TodoRead = exports.createTaskRunTool = exports.BashKill = void 0;
17
+ exports.FsMultiEdit = exports.FsGrep = exports.FsGlob = exports.FsEdit = exports.FsWrite = exports.FsRead = exports.SandboxFactory = exports.GeminiProvider = exports.OpenAIProvider = exports.AnthropicProvider = exports.OpenSandboxFS = exports.OpenSandbox = exports.E2BTemplateBuilder = exports.E2BFS = exports.E2BSandbox = exports.LocalSandbox = exports.PostgresStore = exports.SqliteStore = exports.createExtendedStore = exports.createStore = exports.JSONStore = exports.ProviderCapabilityError = exports.UnsupportedProviderError = exports.UnsupportedContentBlockError = exports.MultimodalValidationError = exports.ResumeError = exports.PermissionModeRegistry = exports.permissionModes = exports.ToolRunner = exports.TodoManager = exports.MessageQueue = exports.PermissionManager = exports.BreakpointManager = exports.SandboxFileManager = exports.OperationStatus = exports.OperationType = exports.OperationQueue = exports.SkillsManagementManager = exports.SkillsManager = exports.TimeBridge = exports.TodoService = exports.AgentTemplateRegistry = exports.FilePool = exports.ContextManager = exports.HookManager = exports.EventBus = exports.Scheduler = exports.Room = exports.AgentPool = exports.Agent = void 0;
18
+ exports.generateAgentId = exports.extendSchema = exports.mergeSchemas = exports.SchemaBuilder = exports.patterns = exports.schema = exports.inferFromExample = exports.createScriptsTool = exports.createSkillsTool = exports.toolMethod = exports.ToolKit = exports.disconnectAllMCP = exports.disconnectMCP = exports.getMCPTools = exports.tools = exports.tool = exports.extractTools = exports.defineTools = exports.defineTool = exports.globalToolRegistry = exports.ToolRegistry = exports.builtin = exports.TodoWrite = exports.TodoRead = exports.createTaskRunTool = exports.BashKill = exports.BashLogs = exports.BashRun = void 0;
19
19
  // Core
20
20
  var agent_1 = require("./core/agent");
21
21
  Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return agent_1.Agent; } });
@@ -84,6 +84,9 @@ var e2b_1 = require("./infra/e2b");
84
84
  Object.defineProperty(exports, "E2BSandbox", { enumerable: true, get: function () { return e2b_1.E2BSandbox; } });
85
85
  Object.defineProperty(exports, "E2BFS", { enumerable: true, get: function () { return e2b_1.E2BFS; } });
86
86
  Object.defineProperty(exports, "E2BTemplateBuilder", { enumerable: true, get: function () { return e2b_1.E2BTemplateBuilder; } });
87
+ var opensandbox_1 = require("./infra/opensandbox");
88
+ Object.defineProperty(exports, "OpenSandbox", { enumerable: true, get: function () { return opensandbox_1.OpenSandbox; } });
89
+ Object.defineProperty(exports, "OpenSandboxFS", { enumerable: true, get: function () { return opensandbox_1.OpenSandboxFS; } });
87
90
  var provider_1 = require("./infra/provider");
88
91
  Object.defineProperty(exports, "AnthropicProvider", { enumerable: true, get: function () { return provider_1.AnthropicProvider; } });
89
92
  Object.defineProperty(exports, "OpenAIProvider", { enumerable: true, get: function () { return provider_1.OpenAIProvider; } });
@@ -0,0 +1,3 @@
1
+ export { OpenSandbox } from './opensandbox-sandbox';
2
+ export { OpenSandboxFS } from './opensandbox-fs';
3
+ export type { OpenSandboxOptions, OpenSandboxWatchMode } from './types';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenSandboxFS = exports.OpenSandbox = void 0;
4
+ var opensandbox_sandbox_1 = require("./opensandbox-sandbox");
5
+ Object.defineProperty(exports, "OpenSandbox", { enumerable: true, get: function () { return opensandbox_sandbox_1.OpenSandbox; } });
6
+ var opensandbox_fs_1 = require("./opensandbox-fs");
7
+ Object.defineProperty(exports, "OpenSandboxFS", { enumerable: true, get: function () { return opensandbox_fs_1.OpenSandboxFS; } });
@@ -0,0 +1,27 @@
1
+ import type { Sandbox as OpenSandboxClient } from '@alibaba-group/opensandbox';
2
+ import { SandboxFS } from '../sandbox';
3
+ export interface OpenSandboxFSHost {
4
+ workDir: string;
5
+ getOpenSandbox(): OpenSandboxClient;
6
+ }
7
+ export declare class OpenSandboxFS implements SandboxFS {
8
+ private readonly host;
9
+ constructor(host: OpenSandboxFSHost);
10
+ resolve(p: string): string;
11
+ isInside(_p: string): boolean;
12
+ read(p: string): Promise<string>;
13
+ write(p: string, content: string): Promise<void>;
14
+ temp(name?: string): string;
15
+ stat(p: string): Promise<{
16
+ mtimeMs: number;
17
+ }>;
18
+ glob(pattern: string, opts?: {
19
+ cwd?: string;
20
+ ignore?: string[];
21
+ dot?: boolean;
22
+ absolute?: boolean;
23
+ }): Promise<string[]>;
24
+ private hasDotPath;
25
+ private toTimestamp;
26
+ private matchGlob;
27
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OpenSandboxFS = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const minimatch_1 = require("minimatch");
9
+ class OpenSandboxFS {
10
+ constructor(host) {
11
+ this.host = host;
12
+ }
13
+ resolve(p) {
14
+ if (path_1.default.posix.isAbsolute(p))
15
+ return path_1.default.posix.normalize(p);
16
+ return path_1.default.posix.normalize(path_1.default.posix.join(this.host.workDir, p));
17
+ }
18
+ isInside(_p) {
19
+ // OpenSandbox runtime is already isolated at container/sandbox level.
20
+ return true;
21
+ }
22
+ async read(p) {
23
+ const sandbox = this.host.getOpenSandbox();
24
+ const resolved = this.resolve(p);
25
+ return await sandbox.files.readFile(resolved);
26
+ }
27
+ async write(p, content) {
28
+ const sandbox = this.host.getOpenSandbox();
29
+ const resolved = this.resolve(p);
30
+ const dir = path_1.default.posix.dirname(resolved);
31
+ if (dir && dir !== '/') {
32
+ await sandbox.files.createDirectories([{ path: dir, mode: 0o755 }]).catch(() => undefined);
33
+ }
34
+ await sandbox.files.writeFiles([{ path: resolved, data: content }]);
35
+ }
36
+ temp(name) {
37
+ const tempName = name || `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
38
+ return path_1.default.posix.join('/tmp', tempName);
39
+ }
40
+ async stat(p) {
41
+ const sandbox = this.host.getOpenSandbox();
42
+ const resolved = this.resolve(p);
43
+ const info = await sandbox.files.getFileInfo([resolved]);
44
+ const fileInfo = info[resolved] || Object.values(info)[0];
45
+ if (!fileInfo) {
46
+ throw new Error(`File not found: ${resolved}`);
47
+ }
48
+ const mtime = this.toTimestamp(fileInfo.modifiedAt ??
49
+ fileInfo.modified_at ??
50
+ fileInfo.mtime ??
51
+ fileInfo.updatedAt);
52
+ return { mtimeMs: mtime ?? Date.now() };
53
+ }
54
+ async glob(pattern, opts) {
55
+ const sandbox = this.host.getOpenSandbox();
56
+ const searchRoot = opts?.cwd ? this.resolve(opts.cwd) : this.host.workDir;
57
+ const items = await sandbox.files.search({
58
+ path: searchRoot,
59
+ pattern,
60
+ });
61
+ const includeDot = opts?.dot ?? false;
62
+ const ignore = opts?.ignore || [];
63
+ const matched = items
64
+ .map((item) => this.resolve(String(item.path || '')))
65
+ .filter((entry) => {
66
+ if (!entry)
67
+ return false;
68
+ if (!includeDot && this.hasDotPath(entry))
69
+ return false;
70
+ if (ignore.length === 0)
71
+ return true;
72
+ const relToRoot = path_1.default.posix.relative(searchRoot, entry);
73
+ const relToWorkDir = path_1.default.posix.relative(this.host.workDir, entry);
74
+ return !ignore.some((rule) => {
75
+ return (this.matchGlob(rule, relToRoot) ||
76
+ this.matchGlob(rule, relToWorkDir) ||
77
+ this.matchGlob(rule, entry));
78
+ });
79
+ });
80
+ if (opts?.absolute) {
81
+ return matched;
82
+ }
83
+ return matched.map((entry) => path_1.default.posix.relative(this.host.workDir, entry));
84
+ }
85
+ hasDotPath(entry) {
86
+ const normalized = entry.replace(/\\/g, '/');
87
+ return normalized.split('/').some((seg) => seg.startsWith('.') && seg.length > 1);
88
+ }
89
+ toTimestamp(value) {
90
+ if (value == null)
91
+ return undefined;
92
+ if (typeof value === 'number' && Number.isFinite(value))
93
+ return value;
94
+ if (value instanceof Date)
95
+ return value.getTime();
96
+ if (typeof value === 'string') {
97
+ const parsed = Date.parse(value);
98
+ if (Number.isFinite(parsed))
99
+ return parsed;
100
+ }
101
+ return undefined;
102
+ }
103
+ matchGlob(pattern, target) {
104
+ const normalizedPattern = pattern.replace(/\\/g, '/');
105
+ const normalizedTarget = target.replace(/\\/g, '/');
106
+ return (0, minimatch_1.minimatch)(normalizedTarget, normalizedPattern, {
107
+ dot: true,
108
+ nocase: false,
109
+ matchBase: false,
110
+ });
111
+ }
112
+ }
113
+ exports.OpenSandboxFS = OpenSandboxFS;
@@ -0,0 +1,35 @@
1
+ import { Sandbox as OpenSandboxClient } from '@alibaba-group/opensandbox';
2
+ import { Sandbox, SandboxExecResult, SandboxKind } from '../sandbox';
3
+ import { OpenSandboxFS } from './opensandbox-fs';
4
+ import { OpenSandboxOptions } from './types';
5
+ export declare class OpenSandbox implements Sandbox {
6
+ kind: SandboxKind;
7
+ workDir: string;
8
+ fs: OpenSandboxFS;
9
+ private sandbox;
10
+ private readonly options;
11
+ private readonly watchers;
12
+ private readonly watchMode;
13
+ private readonly pollIntervalMs;
14
+ private readonly disposeAction;
15
+ constructor(options: OpenSandboxOptions);
16
+ init(): Promise<void>;
17
+ getOpenSandbox(): OpenSandboxClient;
18
+ getSandboxId(): string | undefined;
19
+ isRunning(): Promise<boolean>;
20
+ exec(cmd: string, opts?: {
21
+ timeoutMs?: number;
22
+ }): Promise<SandboxExecResult>;
23
+ watchFiles(paths: string[], listener: (event: {
24
+ path: string;
25
+ mtimeMs: number;
26
+ }) => void): Promise<string>;
27
+ unwatchFiles(id: string): void;
28
+ dispose(): Promise<void>;
29
+ private buildConnectionConfig;
30
+ private pollWatcher;
31
+ private safeMtime;
32
+ private resolveExecTimeoutMs;
33
+ private startPollingWatcher;
34
+ private startNativeWatcher;
35
+ }
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenSandbox = void 0;
4
+ const opensandbox_1 = require("@alibaba-group/opensandbox");
5
+ const logger_1 = require("../../utils/logger");
6
+ const opensandbox_fs_1 = require("./opensandbox-fs");
7
+ class OpenSandbox {
8
+ constructor(options) {
9
+ this.kind = 'opensandbox';
10
+ this.sandbox = null;
11
+ this.watchers = new Map();
12
+ this.options = { ...options };
13
+ this.workDir = options.workDir || '/workspace';
14
+ this.fs = new opensandbox_fs_1.OpenSandboxFS(this);
15
+ this.watchMode = options.watch?.mode || 'polling';
16
+ this.pollIntervalMs = Math.max(100, options.watch?.pollIntervalMs ?? 1000);
17
+ this.disposeAction = options.lifecycle?.disposeAction || 'kill';
18
+ }
19
+ async init() {
20
+ if (this.sandbox)
21
+ return;
22
+ const connectionConfig = this.buildConnectionConfig();
23
+ if (this.options.sandboxId) {
24
+ const connectOptions = {
25
+ sandboxId: this.options.sandboxId,
26
+ connectionConfig,
27
+ skipHealthCheck: this.options.skipHealthCheck,
28
+ readyTimeoutSeconds: this.options.readyTimeoutSeconds,
29
+ healthCheckPollingInterval: this.options.healthCheckPollingInterval,
30
+ };
31
+ this.sandbox = await opensandbox_1.Sandbox.connect(connectOptions);
32
+ }
33
+ else {
34
+ const createOptions = {
35
+ connectionConfig,
36
+ image: this.options.image || this.options.template || 'ubuntu',
37
+ timeoutSeconds: Math.max(1, Math.ceil((this.options.timeoutMs ?? 10 * 60 * 1000) / 1000)),
38
+ env: this.options.env,
39
+ metadata: this.options.metadata,
40
+ resource: this.options.resource,
41
+ networkPolicy: this.options.networkPolicy,
42
+ skipHealthCheck: this.options.skipHealthCheck,
43
+ readyTimeoutSeconds: this.options.readyTimeoutSeconds,
44
+ healthCheckPollingInterval: this.options.healthCheckPollingInterval,
45
+ };
46
+ this.sandbox = await opensandbox_1.Sandbox.create(createOptions);
47
+ }
48
+ // Persist resolved sandbox id for Agent resume metadata.
49
+ this.options.sandboxId = this.sandbox.id;
50
+ // Best-effort workdir bootstrap.
51
+ if (this.workDir && this.workDir !== '/') {
52
+ await this.sandbox.commands
53
+ .run(`mkdir -p ${quoteShell(this.workDir)}`, {
54
+ workingDirectory: '/',
55
+ timeoutSeconds: 10,
56
+ })
57
+ .catch(() => undefined);
58
+ }
59
+ }
60
+ getOpenSandbox() {
61
+ if (!this.sandbox) {
62
+ throw new Error('OpenSandbox not initialized. Call init() first.');
63
+ }
64
+ return this.sandbox;
65
+ }
66
+ getSandboxId() {
67
+ return this.sandbox?.id || this.options.sandboxId;
68
+ }
69
+ async isRunning() {
70
+ try {
71
+ const info = await this.getOpenSandbox().getInfo();
72
+ const state = String(info?.status?.state || '').toLowerCase();
73
+ return state === 'running';
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ async exec(cmd, opts) {
80
+ const sandbox = this.getOpenSandbox();
81
+ const timeoutMs = this.resolveExecTimeoutMs(opts);
82
+ const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
83
+ try {
84
+ const execution = await sandbox.commands.run(cmd, {
85
+ workingDirectory: this.workDir,
86
+ timeoutSeconds,
87
+ });
88
+ const stdout = execution.logs.stdout.map((m) => m.text).join('');
89
+ let stderr = execution.logs.stderr.map((m) => m.text).join('');
90
+ let code = execution.error ? 1 : 0;
91
+ if (execution.id) {
92
+ try {
93
+ const status = await sandbox.commands.getCommandStatus(execution.id);
94
+ if (typeof status.exitCode === 'number') {
95
+ code = status.exitCode;
96
+ }
97
+ else if (status.running === false && status.error) {
98
+ code = 1;
99
+ }
100
+ }
101
+ catch {
102
+ // keep fallback code when status API is unavailable
103
+ }
104
+ }
105
+ if (execution.error && !stderr) {
106
+ const traces = Array.isArray(execution.error.traceback) ? execution.error.traceback.join('\n') : '';
107
+ stderr = [execution.error.name, execution.error.value, traces].filter(Boolean).join('\n');
108
+ }
109
+ return { code, stdout, stderr };
110
+ }
111
+ catch (error) {
112
+ return {
113
+ code: 1,
114
+ stdout: '',
115
+ stderr: error?.message || String(error),
116
+ };
117
+ }
118
+ }
119
+ async watchFiles(paths, listener) {
120
+ if (this.watchMode === 'off') {
121
+ return `watch-disabled-${Date.now()}`;
122
+ }
123
+ const id = `opensandbox-watch-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
124
+ const resolved = Array.from(new Set(paths.map((p) => this.fs.resolve(p))));
125
+ if (this.watchMode === 'native') {
126
+ const nativeStarted = await this.startNativeWatcher(id, resolved, listener);
127
+ if (nativeStarted) {
128
+ return id;
129
+ }
130
+ logger_1.logger.warn('[OpenSandbox] native watch unavailable, falling back to polling mode.');
131
+ }
132
+ await this.startPollingWatcher(id, resolved, listener);
133
+ return id;
134
+ }
135
+ unwatchFiles(id) {
136
+ const watcher = this.watchers.get(id);
137
+ if (!watcher)
138
+ return;
139
+ if (watcher.kind === 'polling') {
140
+ clearInterval(watcher.timer);
141
+ }
142
+ else {
143
+ watcher.abortController.abort();
144
+ }
145
+ this.watchers.delete(id);
146
+ }
147
+ async dispose() {
148
+ for (const id of Array.from(this.watchers.keys())) {
149
+ this.unwatchFiles(id);
150
+ }
151
+ if (!this.sandbox)
152
+ return;
153
+ let disposeError;
154
+ const sandbox = this.sandbox;
155
+ this.sandbox = null;
156
+ if (this.disposeAction === 'kill') {
157
+ try {
158
+ await sandbox.kill();
159
+ }
160
+ catch (error) {
161
+ disposeError = error;
162
+ }
163
+ }
164
+ try {
165
+ await sandbox.close();
166
+ }
167
+ catch (error) {
168
+ disposeError = disposeError || error;
169
+ }
170
+ if (disposeError) {
171
+ throw disposeError;
172
+ }
173
+ }
174
+ buildConnectionConfig() {
175
+ const config = {
176
+ apiKey: this.options.apiKey,
177
+ domain: this.options.endpoint || this.options.domain,
178
+ protocol: this.options.protocol,
179
+ requestTimeoutSeconds: this.options.requestTimeoutSeconds,
180
+ useServerProxy: this.options.useServerProxy ?? false,
181
+ };
182
+ return new opensandbox_1.ConnectionConfig(config);
183
+ }
184
+ async pollWatcher(id, listener) {
185
+ const watcher = this.watchers.get(id);
186
+ if (!watcher || watcher.kind !== 'polling' || watcher.polling)
187
+ return;
188
+ watcher.polling = true;
189
+ try {
190
+ do {
191
+ watcher.pending = false;
192
+ for (const path of watcher.paths) {
193
+ if (!this.watchers.has(id)) {
194
+ return;
195
+ }
196
+ const current = await this.safeMtime(path);
197
+ const previous = watcher.lastMtimes.get(path);
198
+ watcher.lastMtimes.set(path, current);
199
+ if (previous === undefined && current === undefined)
200
+ continue;
201
+ if (previous === current)
202
+ continue;
203
+ listener({ path, mtimeMs: current ?? Date.now() });
204
+ }
205
+ } while (this.watchers.has(id) && watcher.pending);
206
+ }
207
+ finally {
208
+ if (this.watchers.get(id) === watcher) {
209
+ watcher.polling = false;
210
+ }
211
+ }
212
+ }
213
+ async safeMtime(path) {
214
+ try {
215
+ const stat = await this.fs.stat(path);
216
+ return stat.mtimeMs;
217
+ }
218
+ catch {
219
+ return undefined;
220
+ }
221
+ }
222
+ resolveExecTimeoutMs(opts) {
223
+ return opts?.timeoutMs ?? this.options.execTimeoutMs ?? this.options.timeoutMs ?? 120000;
224
+ }
225
+ async startPollingWatcher(id, paths, listener) {
226
+ const lastMtimes = new Map();
227
+ for (const p of paths) {
228
+ lastMtimes.set(p, await this.safeMtime(p));
229
+ }
230
+ const watcher = {
231
+ kind: 'polling',
232
+ timer: setInterval(() => {
233
+ const current = this.watchers.get(id);
234
+ if (!current || current.kind !== 'polling')
235
+ return;
236
+ current.pending = true;
237
+ if (!current.polling) {
238
+ void this.pollWatcher(id, listener);
239
+ }
240
+ }, this.pollIntervalMs),
241
+ paths,
242
+ lastMtimes,
243
+ polling: false,
244
+ pending: true,
245
+ };
246
+ this.watchers.set(id, watcher);
247
+ void this.pollWatcher(id, listener);
248
+ }
249
+ async startNativeWatcher(id, paths, listener) {
250
+ const probe = await this.exec('command -v inotifywait >/dev/null 2>&1 && echo __KODE_INOTIFY_READY__', {
251
+ timeoutMs: 5000,
252
+ });
253
+ if (probe.code !== 0 || !probe.stdout.includes('__KODE_INOTIFY_READY__')) {
254
+ return false;
255
+ }
256
+ const sandbox = this.getOpenSandbox();
257
+ const abortController = new AbortController();
258
+ const nativeWatchCommand = buildNativeWatchCommand(paths);
259
+ let stdoutBuffer = '';
260
+ const streamTask = (async () => {
261
+ try {
262
+ for await (const event of sandbox.commands.runStream(nativeWatchCommand, { workingDirectory: this.workDir }, abortController.signal)) {
263
+ if (abortController.signal.aborted) {
264
+ break;
265
+ }
266
+ if (event.type !== 'stdout' || typeof event.text !== 'string') {
267
+ continue;
268
+ }
269
+ stdoutBuffer += event.text;
270
+ let lineBreak = stdoutBuffer.indexOf('\n');
271
+ while (lineBreak >= 0) {
272
+ const line = stdoutBuffer.slice(0, lineBreak).trim();
273
+ stdoutBuffer = stdoutBuffer.slice(lineBreak + 1);
274
+ if (line) {
275
+ listener({ path: line, mtimeMs: Date.now() });
276
+ }
277
+ lineBreak = stdoutBuffer.indexOf('\n');
278
+ }
279
+ }
280
+ const tail = stdoutBuffer.trim();
281
+ if (tail) {
282
+ listener({ path: tail, mtimeMs: Date.now() });
283
+ }
284
+ }
285
+ catch (error) {
286
+ if (!abortController.signal.aborted) {
287
+ logger_1.logger.warn('[OpenSandbox] native watch stream failed, fallback to polling.', error);
288
+ }
289
+ }
290
+ finally {
291
+ const current = this.watchers.get(id);
292
+ if (!current || current.kind !== 'native' || current.abortController !== abortController) {
293
+ return;
294
+ }
295
+ this.watchers.delete(id);
296
+ if (!abortController.signal.aborted) {
297
+ try {
298
+ await this.startPollingWatcher(id, paths, listener);
299
+ logger_1.logger.warn('[OpenSandbox] native watch stopped, switched to polling mode.');
300
+ }
301
+ catch (error) {
302
+ logger_1.logger.warn('[OpenSandbox] failed to start polling fallback after native watch exit.', error);
303
+ }
304
+ }
305
+ }
306
+ })();
307
+ const watcher = {
308
+ kind: 'native',
309
+ paths,
310
+ abortController,
311
+ streamTask,
312
+ };
313
+ this.watchers.set(id, watcher);
314
+ return true;
315
+ }
316
+ }
317
+ exports.OpenSandbox = OpenSandbox;
318
+ function quoteShell(value) {
319
+ return `'${value.replace(/'/g, `'\\''`)}'`;
320
+ }
321
+ function buildNativeWatchCommand(paths) {
322
+ const targets = paths.map((p) => quoteShell(p)).join(' ');
323
+ const script = `exec inotifywait -m -e modify,create,delete,move --format '%w%f' -- ${targets}`;
324
+ return `sh -lc ${quoteShell(script)}`;
325
+ }
@@ -0,0 +1,30 @@
1
+ export type OpenSandboxWatchMode = 'native' | 'polling' | 'off';
2
+ export interface OpenSandboxOptions {
3
+ kind: 'opensandbox';
4
+ apiKey?: string;
5
+ endpoint?: string;
6
+ domain?: string;
7
+ protocol?: 'http' | 'https';
8
+ sandboxId?: string;
9
+ image?: string;
10
+ template?: string;
11
+ workDir?: string;
12
+ timeoutMs?: number;
13
+ execTimeoutMs?: number;
14
+ requestTimeoutSeconds?: number;
15
+ useServerProxy?: boolean;
16
+ env?: Record<string, string>;
17
+ metadata?: Record<string, string>;
18
+ resource?: Record<string, string>;
19
+ networkPolicy?: Record<string, any>;
20
+ skipHealthCheck?: boolean;
21
+ readyTimeoutSeconds?: number;
22
+ healthCheckPollingInterval?: number;
23
+ watch?: {
24
+ mode?: OpenSandboxWatchMode;
25
+ pollIntervalMs?: number;
26
+ };
27
+ lifecycle?: {
28
+ disposeAction?: 'close' | 'kill';
29
+ };
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -82,8 +82,8 @@ function normalizeBaseUrl(url) {
82
82
  }
83
83
  function normalizeOpenAIBaseUrl(url) {
84
84
  let normalized = url.replace(/\/+$/, '');
85
- // Auto-append /v1 if not present (for OpenAI-compatible APIs)
86
- if (!normalized.endsWith('/v1')) {
85
+ // Auto-append /v1 if no version path detected (e.g., /v1, /v2, /v4)
86
+ if (!/\/v\d+$/.test(normalized)) {
87
87
  normalized += '/v1';
88
88
  }
89
89
  return normalized;
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SandboxFactory = void 0;
4
4
  const sandbox_1 = require("./sandbox");
5
5
  const e2b_sandbox_1 = require("./e2b/e2b-sandbox");
6
+ const opensandbox_1 = require("./opensandbox");
6
7
  class SandboxFactory {
7
8
  constructor() {
8
9
  this.factories = new Map();
9
10
  this.factories.set('local', (config) => new sandbox_1.LocalSandbox(config));
10
11
  this.factories.set('e2b', (config) => new e2b_sandbox_1.E2BSandbox(config));
12
+ this.factories.set('opensandbox', (config) => new opensandbox_1.OpenSandbox(config));
11
13
  }
12
14
  register(kind, factory) {
13
15
  this.factories.set(kind, factory);
@@ -24,6 +26,9 @@ class SandboxFactory {
24
26
  if (config.kind === 'e2b' && sandbox instanceof e2b_sandbox_1.E2BSandbox) {
25
27
  await sandbox.init();
26
28
  }
29
+ if (config.kind === 'opensandbox' && sandbox instanceof opensandbox_1.OpenSandbox) {
30
+ await sandbox.init();
31
+ }
27
32
  return sandbox;
28
33
  }
29
34
  }
@@ -1,4 +1,4 @@
1
- export type SandboxKind = 'local' | 'docker' | 'k8s' | 'remote' | 'vfs' | 'e2b';
1
+ export type SandboxKind = 'local' | 'docker' | 'k8s' | 'remote' | 'vfs' | 'e2b' | 'opensandbox';
2
2
  export interface SandboxFS {
3
3
  resolve(path: string): string;
4
4
  isInside(path: string): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shareai-lab/kode-sdk",
3
- "version": "2.7.3",
3
+ "version": "2.7.4",
4
4
  "description": "Event-driven, long-running AI Agent development framework with enterprise-grade persistence and context management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,6 +22,7 @@
22
22
  "example:room": "ts-node examples/03-room-collab.ts",
23
23
  "example:scheduler": "ts-node examples/04-scheduler-watch.ts",
24
24
  "example:nextjs": "ts-node examples/nextjs-api-route.ts",
25
+ "example:opensandbox": "ts-node examples/opensandbox-usage.ts",
25
26
  "example:openrouter": "ts-node examples/05-openrouter-complete.ts",
26
27
  "example:openrouter-stream": "ts-node examples/06-openrouter-stream.ts",
27
28
  "example:openrouter-agent": "ts-node examples/07-openrouter-agent.ts",
@@ -42,12 +43,14 @@
42
43
  "author": "",
43
44
  "license": "MIT",
44
45
  "dependencies": {
46
+ "@alibaba-group/opensandbox": "~0.1.4",
45
47
  "@modelcontextprotocol/sdk": "~1.22.0",
46
48
  "ajv": "^8.17.1",
47
49
  "better-sqlite3": "^12.6.2",
48
50
  "dotenv": "^16.4.5",
49
51
  "e2b": "^2.10.3",
50
52
  "fast-glob": "^3.3.2",
53
+ "minimatch": "^10.2.4",
51
54
  "pg": "^8.17.2",
52
55
  "undici": "^7.18.2",
53
56
  "zod": "^4.3.5",