@voidrun/sdk
v0.0.40
Published
VoidRun AI Sandbox SDK
Downloads
1,080
Readme
VoidRun TypeScript SDK
A powerful, type-safe SDK for interacting with VoidRun AI Sandboxes. Execute code, manage files, watch file changes, and interact with pseudo-terminals in isolated environments.
The public surface is aligned with the VoidRun Python SDK (createSandbox, listSandboxes, remove, runCode, shared defaults).
Table of contents
- Features
- Requirements
- Installation
- Configuration
- Quick start
- Core concepts
- Code execution
- Code interpreter
- Background commands
- File operations
- File watching
- Pseudo-terminal (PTY)
- API reference
- Examples
- Error handling
- Testing and examples runner
- Building from source
- Publishing
- Troubleshooting
Features
- 🏗️ Sandbox Management - Create, list, start, stop, pause, resume, and remove sandboxes
- 🚀 Code Execution - Execute commands with real-time streaming output capture
- 📁 File Operations - Create, read, delete, compress, and extract files
- 👀 File Watching - Monitor file changes in real-time via WebSocket
- 💻 Pseudo-Terminal (PTY) - Interactive terminal sessions (ephemeral & persistent)
- 🧠 Code Interpreter - Easy multi-language code execution (Python, JavaScript, Bash)
- ⚡ Background Commands - Run, list, kill, and attach to background processes
- 🔐 Type-Safe - Full TypeScript support with generated types from OpenAPI
- 🎯 Async/await - Promise-based API throughout
Requirements
- Node.js 18+ recommended (ESM package; Node 20+ for
tsxexamples) - TypeScript 5.x when compiling from source
Installation
npm install @voidrun/sdkOr with yarn:
yarn add @voidrun/sdkConfiguration
VoidRun requires an API key. On the hosted platform the base URL defaults to BASE_PATH (see src/api-client/runtime.ts: https://platform.void-run.com/api without a trailing slash). Set VR_API_URL or pass baseUrl only when you target a self-hosted API.
import { VoidRun } from "@voidrun/sdk";
const vr = new VoidRun({
apiKey: process.env.VR_API_KEY!, // or pass a string literal
});| Environment variable | Purpose |
|--------------------|---------|
| VR_API_KEY | API key when not passed to the constructor. |
| VR_API_URL | (Self-hosted only.) Overrides the packaged default API base URL. |
createSandbox defaults (when you omit fields) match the Python SDK: image code, 1 CPU, 1024 MB memory.
For self-hosted VoidRun, set VR_API_URL (or baseUrl) to your instance’s API root (including /api if that is how your server is mounted).
Quick start
Basic usage
import { VoidRun } from "@voidrun/sdk";
const vr = new VoidRun({
apiKey: "your-api-key-here",
});
const sandbox = await vr.createSandbox({});
// Exec returns ExecResponse: stdout/stderr/exitCode live on .data (ExecResponseData)
const result = await sandbox.exec({ command: 'echo "Hello from VoidRun"' });
console.log(result.data?.stdout);
await sandbox.remove();Core concepts
Sandboxes
An isolated environment where you can execute code, manage files, and run terminals.
// Create a sandbox with options
const sandbox = await vr.createSandbox({
name: "my-sandbox", // Optional: Sandbox name
mem: 1024, // Memory in MB (optional, has defaults)
cpu: 1, // CPU cores (optional, has defaults)
image: "code", // Optional: Image name. Valid values are code, code:1.42.4. Check dashboard for more system images
envVars: { // Optional: Environment variables
DEBUG: 'true',
LOG_LEVEL: 'info'
}
});
// List all sandboxes
const { sandboxes, meta } = await vr.listSandboxes();
console.log(`Total sandboxes: ${sandboxes.length}`);
// Get a specific sandbox
const existingSandbox = await vr.getSandbox(sandboxId);
// Sandbox lifecycle management
await sandbox.start(); // Start a stopped sandbox
await sandbox.stop(); // Stop a running sandbox
await sandbox.pause(); // Pause a running sandbox
await sandbox.resume(); // Resume a paused sandbox
// Remove a sandbox
await sandbox.remove();Code execution
Execute commands and capture output, errors, and exit codes.
Non-streaming exec resolves to an ExecResponse from the OpenAPI client: use result.data for ExecResponseData (stdout, stderr, exitCode).
Synchronous execution
const result = await sandbox.exec({ command: "ls -la /home" });
console.log(result.data?.stdout); // standard output
console.log(result.data?.stderr); // standard error
console.log(result.data?.exitCode); // exit codeStreaming execution (SSE)
For real-time output, provide streaming handlers:
await sandbox.exec({
command: "seq 1 10 | while read i; do echo \"Line $i\"; sleep 1; done"
}, {
onStdout: (data) => console.log('stdout:', data),
onStderr: (data) => console.log('stderr:', data),
onExit: (result) => console.log('exit:', result),
onError: (error) => console.error('error:', error),
signal: abortController.signal // Optional: AbortSignal for cancellation
});Execution with options
const result = await sandbox.exec({
command: "echo $MY_VAR && pwd",
cwd: "/tmp", // Working directory
env: { MY_VAR: "test_value" }, // Environment variables
timeout: 30 // Timeout in seconds
});Code interpreter
Execute code in multiple programming languages with a simple, intuitive API.
// Execute Python code
const result = await sandbox.runCode('print(2 + 2)', { language: 'python' });
console.log(result.stdout.trim()); // "4"
console.log(result.success); // true
// Execute JavaScript code
const jsResult = await sandbox.runCode('console.log("Hello")', { language: 'javascript' });
// Execute with streaming output
const streamResult = await sandbox.runCode(`
for i in range(5):
print(f"Iteration {i}")
`, {
language: 'python',
onStdout: (data) => console.log(data),
onStderr: (data) => console.error(data),
});
// Check execution result
console.log(streamResult.exitCode); // 0 for success
console.log(streamResult.results); // Parsed results
console.log(streamResult.logs); // { stdout: [...], stderr: [...] }Supported Languages: python, javascript, typescript, node, bash, sh
Background commands
Run long-running processes in the background and manage them.
// Start a background process
const runResult = await sandbox.commands.run(
"sleep 100 && echo 'Done'", // command
{ DEBUG: 'true' }, // env (optional)
"/tmp", // cwd (optional)
0 // timeout (0 = no timeout)
);
console.log(runResult.pid); // Process ID
// List all running processes
const listResult = await sandbox.commands.list();
console.log(listResult.processes); // Array of ProcessInfo
// Attach to a process and stream output
await sandbox.commands.connect(runResult.pid, {
onStdout: (data) => console.log(data),
onStderr: (data) => console.error(data),
onExit: ({ exitCode }) => console.log('Process exited:', exitCode),
});
// Wait for a process to complete
const waitResult = await sandbox.commands.wait(runResult.pid);
console.log(waitResult.exitCode);
// Kill a running process
const killResult = await sandbox.commands.kill(runResult.pid);
console.log(killResult.success);File operations
Create, read, update, and manage files in the sandbox.
// Create a file
await sandbox.fs.createFile("/tmp/hello.txt");
// Upload content to file
await sandbox.fs.uploadFile("/tmp/hello.txt", "Hello, World!");
// Upload via stream
const stream = Readable.from(["line 1\n", "line 2\n"]);
await sandbox.fs.uploadFileStream("/tmp/streamed.txt", Readable.toWeb(stream));
// Read a file
const buffer = await sandbox.fs.downloadFile("/tmp/hello.txt");
const content = buffer.toString();
// Download as stream
const fileStream = await sandbox.fs.downloadFileStream("/tmp/hello.txt");
const reader = fileStream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
// Delete a file
await sandbox.fs.deleteFile("/tmp/hello.txt");
// List directory
const result = await sandbox.fs.listFiles("/tmp");
const files = result.data?.files;
// Get file stats
const stats = await sandbox.fs.statFile("/tmp/hello.txt");
// Create directory
await sandbox.fs.createDirectory("/tmp/mydir");
// Move file
await sandbox.fs.moveFile("/tmp/file.txt", "/tmp/newfile.txt");
// Copy file
await sandbox.fs.copyFile("/tmp/file.txt", "/tmp/copy.txt");
// Change permissions
await sandbox.fs.changePermissions("/tmp/file.txt", "755");
// Head/Tail - read first or last lines
const head = await sandbox.fs.headTail("/tmp/file.txt", { head: true, lines: 10 });
const tail = await sandbox.fs.headTail("/tmp/file.txt", { head: false, lines: 10 });
// Search files by pattern
const search = await sandbox.fs.searchFiles("/tmp", "*.txt");
// Get folder size
const size = await sandbox.fs.folderSize("/tmp");
// Compress files
const archive = await sandbox.fs.compressFile("/tmp", "tar.gz");
console.log(archive.archivePath || archive.data?.archivePath);
// Extract archive
await sandbox.fs.extractArchive("/tmp/archive.tar.gz", "/tmp/extracted");File watching
Monitor file changes in real-time.
const watcher = await sandbox.fs.watch("/app", {
recursive: true,
onEvent: (event) => {
console.log(`File changed: ${event.path} - ${event.type}`);
},
onError: (err) => {
console.error("Watch error:", err);
},
onClose: () => {
console.log("Watcher closed");
}
});
// Stop watching
watcher.close();Pseudo-terminal (PTY)
Interactive terminal sessions with two modes:
Ephemeral sessions (temporary)
// No session management - temporary shell
const pty = await sandbox.pty.connect({
onData: (data) => {
process.stdout.write(data);
},
onError: (err) => {
console.error("PTY error:", err);
},
});
// Send commands
pty.sendInput('echo "Hello"\n');
pty.sendInput("pwd\n");
// Close connection
pty.close();Persistent sessions
// Create a persistent session (CreatePTYSession200Response)
const response = await sandbox.pty.createSession();
const sessionId = response.data?.sessionId;
// Connect to the session
const pty = await sandbox.pty.connect({
sessionId,
onData: (data) => {
process.stdout.write(data);
},
});
// Send commands
pty.sendInput('echo "Hello"\n');
// Close connection (session persists)
pty.close();
// Reconnect later - session and output persist
const reconnected = await sandbox.pty.connect({
sessionId,
onData: (data) => {
process.stdout.write(data); // Includes buffered output
},
});Interactive commands
Run commands with automatic prompt detection:
const pty = await sandbox.pty.connect({ sessionId });
const output = await pty.runCommand("ls -la", {
timeout: 5000,
prompt: /[#$] $/, // Regex to detect shell prompt
});
console.log("Output:", output);Resize terminal
pty.resize(80, 24); // columns, rowsSession management
// List all sessions
const sessions = await sandbox.pty.list();
// Delete a session
await sandbox.pty.deleteSession(sessionId);API reference
VoidRun class
Main client for interacting with the API.
new VoidRun(options?: VoidRunConfig)Options:
apiKey?: string- API key (defaults toprocess.env.VR_API_KEY; empty throws)
Methods:
createSandbox(options: SandboxOptions)- Create a new sandboxname?: string- Sandbox nameimage?: string- Image id (defaults tocodewhen omitted)cpu?: number- CPU cores (default1)mem?: number- Memory in MB (default1024)orgId?: string- Organization IDuserId?: string- User IDsync?: boolean- Sync mode (default: true)envVars?: Record<string, string>- Environment variablesautoSleep?: boolean,region?: string,refId?: string- optional passthrough fields
listSandboxes(options?: { page?: number; limit?: number })- Returns{ sandboxes, meta }wheremetahastotal,page,limit,totalPagesgetSandbox(id: string)- Get a specific sandboxremoveSandbox(id: string)- Delete a sandbox by id
Sandbox class
Represents an isolated sandbox environment.
Properties:
id: string- Sandbox IDname: string- Sandbox namecpu: number- CPU coresmem: number- Memory in MBorgId: string- Organization IDcreatedAt: Date- Creation timestampcreatedBy: string- Creator IDstatus: string- Sandbox statusenvVars?: { [key: string]: string }- Environment variablesregion?: string,refId?: string,autoSleep?: boolean- Optional metadatafs: FS- File system interfacepty: PTY- PTY interfaceinterpreter: CodeInterpreter- Code interpretercommands: Commands- Background commands interface
Methods:
exec(request: ExecRequest, handlers?: ExecStreamHandlers)- Execute a commandrequest.command: string- Command to executerequest.cwd?: string- Working directoryrequest.env?: Record<string, string>- Environment variablesrequest.timeout?: number- Timeout in secondshandlers.onStdout?: (data: string) => void- Stream stdouthandlers.onStderr?: (data: string) => void- Stream stderrhandlers.onExit?: (result: ExecStreamExit) => void- Exit handlerhandlers.onError?: (error: Error) => void- Error handlerhandlers.signal?: AbortSignal- Abort signal
execStream(request: ExecRequest, handlers: ExecStreamHandlers)- Streaming executionrunCode(code: string, options?: CodeExecutionOptions)- Execute codestart()- Start the sandboxstop()- Stop the sandboxpause()- Pause the sandboxresume()- Resume the sandboxremove()- Delete the sandboxinfo()- Returns the same sandbox instance (Promise<this>)
Exec response (ExecResponse):
// Returned from non-streaming exec()
{
status?: string;
message?: string;
data?: {
stdout?: string;
stderr?: string;
exitCode?: number;
};
}Code Execution Result:
{
success: boolean;
results: any; // Parsed results
stdout: string; // Combined stdout
stderr: string; // Combined stderr
error?: string; // Error message if any
exitCode?: number; // Process exit code
logs: {
stdout: string[]; // Individual stdout lines
stderr: string[]; // Individual stderr lines
};
}Commands class
Background process management.
Methods:
run(command: string, env?: Record<string, string>, cwd?: string, timeout?: number)- Start a background processlist()- List all running processeskill(pid: number)- Kill a processconnect(pid: number, handlers: ProcessAttachHandlers)- Attach to process output streamwait(pid: number)- Wait for process to complete
FS (file system) class
Manage files and directories.
Methods:
createFile(path: string)- Create a fileuploadFile(path: string, content: string)- Upload file contentuploadFileStream(path: string, stream: ReadableStream)- Upload file as streamdownloadFile(path: string)- Download file as BufferdownloadFileStream(path: string)- Download file as ReadableStreamdeleteFile(path: string)- Delete a file/directorylistFiles(path: string)- List directory contentsstatFile(path: string)- Get file metadatacreateDirectory(path: string)- Create a directorycompressFile(path: string, format: 'tar' | 'tar.gz' | 'tar.bz2' | 'zip')- Create archiveextractArchive(archivePath: string, destPath?: string)- Extract archivemoveFile(from: string, to: string)- Move/rename filecopyFile(from: string, to: string)- Copy filechangePermissions(path: string, mode: string)- Change file permissionsheadTail(path: string, options?: { lines?: number; head?: boolean })- Read file head/tailsearchFiles(path: string, pattern: string)- Search files by patternfolderSize(path: string)- Get folder sizewatch(path: string, options: FileWatchOptions)- Watch for file changes
FileWatcher
Monitor file changes in real-time.
Methods:
watch(path: string, options: FileWatchOptions)- Start watching a pathrecursive?: boolean- Watch subdirectoriesonEvent(event: FileChangeEvent)- Called on file changeonError(error: Error)- Called on erroronClose()- Called when watcher closes (optional)
Watcher Methods:
close()- Stop watching
PTY class
Pseudo-terminal operations.
Methods:
list()- List active sessionscreateSession()- Create a persistent sessionconnect(options: PtyOptions)- Connect to PTYsessionId?: string- For persistent sessionsonData(data: string)- Receive dataonError(error: Error)- Handle errorsonClose()- Connection closed (optional)
deleteSession(sessionId: string)- Delete a session
PtySession Methods:
sendInput(data: string)- Send data to terminalrunCommand(cmd: string, options: RunCommandOptions)- Execute with prompt detectionresize(cols: number, rows: number)- Resize terminalclose()- Close connection
Examples
Execute Python Script
import { VoidRun } from "@voidrun/sdk";
const vr = new VoidRun({});
const sandbox = await vr.createSandbox({ mem: 1024, cpu: 1 });
// Create Python script
await sandbox.fs.createFile("/tmp/script.py");
await sandbox.fs.uploadFile(
"/tmp/script.py",
`
import sys
print("Python version:", sys.version)
print("Hello from Python!")
`,
);
const result = await sandbox.exec({ command: "python3 /tmp/script.py" });
console.log(result.data?.stdout);
await sandbox.remove();Code Interpreter Workflow
const sandbox = await vr.createSandbox({ name: 'interpreter-demo' });
// Python data analysis
const result = await sandbox.runCode(`
import json
data = [1, 2, 3, 4, 5]
result = {
"sum": sum(data),
"avg": sum(data) / len(data),
"max": max(data)
}
print(json.dumps(result))
`, { language: 'python' });
console.log(result.results); // Parsed JSON output
// JavaScript
const jsResult = await sandbox.runCode(`
const fib = (n) => n <= 1 ? n : fib(n-1) + fib(n-2);
console.log(fib(10));
`, { language: 'javascript' });
await sandbox.remove();Background Process Management
const sandbox = await vr.createSandbox({});
// Start a long-running process
const { pid } = await sandbox.commands.run("tail -f /var/log/syslog");
// Attach to stream output
await sandbox.commands.connect(pid, {
onStdout: (data) => console.log(data),
onExit: ({ exitCode }) => console.log('Exited:', exitCode),
});
// Later, kill the process
await sandbox.commands.kill(pid);
await sandbox.remove();Monitor Code Changes
const sandbox = await vr.createSandbox({ mem: 1024, cpu: 1 });
// Watch for TypeScript file changes
const watcher = await sandbox.fs.watch("/app/src", {
recursive: true,
onEvent: async (event) => {
console.log(`File ${event.type}: ${event.path}`);
// Auto-compile on change
await sandbox.exec({ command: "npm run build" });
},
});
// Clean up
setTimeout(() => watcher.close(), 60000);Build & Test Workflow
const sandbox = await vr.createSandbox({ mem: 2048, cpu: 2 });
// Upload source code
const sourceCode = `console.log('Hello World');`;
await sandbox.fs.createFile("/app/main.js");
await sandbox.fs.uploadFile("/app/main.js", sourceCode);
// Install dependencies
let result = await sandbox.exec({ command: "npm install" });
if (result.data?.exitCode !== 0) throw new Error("Install failed");
// Run tests
result = await sandbox.exec({ command: "npm test" });
console.log("Test output:", result.data?.stdout);
// Build
result = await sandbox.exec({ command: "npm run build" });
console.log("Build output:", result.data?.stdout);
await sandbox.remove();Interactive Development Shell
const sandbox = await vr.createSandbox({ mem: 1024, cpu: 1 });
// Create a persistent session
const sessionResp = await sandbox.pty.createSession();
const sessionId = sessionResp.data?.sessionId;
const pty = await sandbox.pty.connect({
sessionId,
onData: (data) => process.stdout.write(data),
onClose: () => console.log("Shell closed"),
});
// Interactive commands
pty.sendInput("npm init -y\n");
await new Promise((r) => setTimeout(r, 1000));
pty.sendInput("npm install express\n");
await new Promise((r) => setTimeout(r, 2000));
pty.sendInput('node -e "console.log(process.version)"\n');
await new Promise((r) => setTimeout(r, 500));
pty.close();
await sandbox.remove();Error handling
Failed HTTP calls from the generated client are thrown as an Error whose message combines status text with the API body when JSON parsing succeeds: fields error, details, and message are concatenated so you see the same hints as in the Python SDK.
try {
const sandbox = await vr.createSandbox({ mem: 1024, cpu: 1 });
// ...
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
}
}Common cases:
- Validation: Invalid sandbox parameters for your org/plan
- Authentication: Missing/invalid API key
- Not found: Wrong sandbox or session id
- Timeout: Network or long-running command limits
Testing and examples runner
From the ts-sdk directory:
npm install
chmod +x scripts/run_all_examples.sh # once
./scripts/run_all_examples.shEach example is run with npx tsx --env-file=.env <file>. Create a .env with at least VR_API_KEY= (add VR_API_URL= only for self-hosted). The script exits with status 1 if any example fails (suitable for CI).
Run a single script:
npx tsx --env-file=.env example/test-sandbox-exec.tsNotable scripts under example/:
test-sandbox-exec.ts: Command execution (incl. streaming)test-sandbox-fs.ts: File system operationstest-sandbox-lifecycle.ts: Sandbox lifecycletest-pty.ts/test-pty-comprehensive.ts: PTYtest-watch.ts: File watchingtest-background-exec.ts: Background commandstest-ts-exec.ts: TypeScript viarunCodecode-interpreter-example.ts: Interpreter workflowtest-commonjs-import.cjs/test-esm-import.mjs: Package exports
Building from source
# Install dependencies
npm install
# Build TypeScript
npm run build
# Clean build artifacts
npm run cleanPublishing
npm run build
npm publish --access public(prepublishOnly in package.json runs a clean build and bumps the patch version: adjust your release workflow if you do not want an automatic version bump.)
Troubleshooting
"API key is required"
Pass the key in the constructor or set VR_API_KEY:
const vr = new VoidRun({ apiKey: "your-api-key" });export VR_API_KEY="your-api-key""Base URL is required"
The resolved base URL is empty (for example VR_API_URL= with no value). Omit it to use the default host, or set VR_API_URL / baseUrl to your self-hosted API root.
"Sandbox creation failed"
Ensure your sandbox parameters are valid:
mem: minimum 1024 MBcpu: minimum 1 core
const sandbox = await vr.createSandbox({
mem: 1024, // At least 1GB
cpu: 1, // At least 1 core
});"PTY Connection Timeout"
Increase timeout for slow systems:
const pty = await sandbox.pty.connect({
sessionId,
onData: (data) => console.log(data),
});
// For runCommand
const output = await pty.runCommand("slow-command", {
timeout: 30000, // 30 seconds
});"File Not Found"
Check the file path:
// List files to verify path
const files = await sandbox.fs.listFiles("/app");
console.log(files.data?.files);
// Then access specific file
const content = await sandbox.fs.downloadFile("/app/file.txt");API Documentation
Full API documentation is available at:
Contributing
Contributions are welcome! Please check the main repository for guidelines.
License
ISC License - See LICENSE file for details
Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
Made with ❤️ by VoidRun
