Skip to main content

Overview

Jinba Toolbox executes tool code in isolated sandbox environments. The sandbox system is designed around a provider-agnostic architecture: a shared interface package (@jinba-toolbox/sandbox-core) defines the contract, and individual provider packages (sandbox-e2b, sandbox-daytona) implement it. This separation means you can switch providers or add new ones without modifying any business logic.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                     API Server                              │
│                                                             │
│  Execution Service ──▶ SandboxProvider interface            │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                  Provider Implementations                   │
│       ┌────────────────┐       ┌─────────────────┐         │
│       │ sandbox-e2b    │       │ sandbox-daytona  │         │
│       │ (E2B Code      │       │ (Daytona SDK)    │         │
│       │  Interpreter)  │       │                  │         │
│       └────────────────┘       └─────────────────┘         │
├─────────────────────────────────────────────────────────────┤
│                    sandbox-core                              │
│           (Interfaces & Types only)                         │
└─────────────────────────────────────────────────────────────┘

Core Interface

The @jinba-toolbox/sandbox-core package contains only interfaces and types — no concrete implementations. All providers must conform to these contracts.

SandboxProvider

The factory that creates and resumes sandbox instances:
interface SandboxProvider {
  /** Create a new sandbox instance */
  create(options: SandboxCreateOptions): Promise<SandboxInstance>;
  /** Resume an existing sandbox by ID */
  resume(sandboxId: string): Promise<SandboxInstance>;
}

SandboxInstance

A running sandbox environment with code execution, shell commands, and filesystem access:
interface SandboxInstance {
  readonly sandboxId: string;
  readonly commands: SandboxCommands;
  readonly filesystem: SandboxFilesystem;

  executeCode(
    code: string,
    language: SandboxLanguage,
    options?: CodeExecutionOptions
  ): Promise<CodeExecutionResult>;

  kill(): Promise<void>;
  pause(): Promise<void>;
  getHost(port: number): Promise<string>;
}

SandboxCommands

Shell command execution within the sandbox:
interface SandboxCommands {
  run(
    command: string,
    options?: SandboxCommandOptions
  ): Promise<SandboxExecutionResult>;
}

SandboxFilesystem

File operations within the sandbox:
interface SandboxFilesystem {
  read(path: string): Promise<string>;
  readBytes(path: string): Promise<Uint8Array>;
  write(path: string, content: string | Uint8Array): Promise<void>;
  list(path: string): Promise<SandboxFileInfo[]>;
  exists(path: string): Promise<boolean>;
  mkdir(path: string): Promise<void>;
  remove(path: string): Promise<void>;
}

Key Types

type SandboxLanguage = "python" | "typescript";

interface SandboxCreateOptions {
  language: SandboxLanguage;
  packages?: string[];               // Packages to install
  envs?: Record<string, string>;     // Environment variables
  workingDirectory?: string;         // Working directory inside sandbox
  timeoutMs?: number;                // Timeout in milliseconds
}

interface CodeExecutionResult {
  success: boolean;
  output?: unknown;       // Parsed JSON output if available
  stdout: string;
  stderr: string;
  durationMs: number;
  error?: {
    name: string;
    message: string;
    traceback?: string;
  };
}

interface SandboxExecutionResult {
  exitCode: number;
  stdout: string;
  stderr: string;
}

E2B Provider

The @jinba-toolbox/sandbox-e2b package implements the sandbox interface using the E2B Code Interpreter SDK.

Configuration

import { createE2BProvider } from "@jinba-toolbox/sandbox-e2b";

const provider = createE2BProvider({
  apiKey: process.env.E2B_API_KEY!,
  templateId: "custom-template",  // Optional custom sandbox template
  timeoutMs: 300000,              // Default: 5 minutes
});
OptionTypeDefaultDescription
apiKeystring(required)E2B API key
templateIdstringundefinedCustom sandbox template ID
timeoutMsnumber300000Default timeout (5 minutes)

How It Works

  1. Sandbox creation: The provider creates an E2B Code Interpreter sandbox, optionally using a custom template.
  2. Package installation: If packages are specified, pip install (Python) or npm install (TypeScript) runs inside the sandbox.
  3. Code execution: Tool code is executed via E2B’s runCode API in a Jupyter-like kernel.
  4. Lifecycle: Sandboxes can be paused, resumed, or terminated.

TypeScript Note

E2B’s Code Interpreter executes TypeScript in a Jupyter kernel that does not support ESM file imports. For this reason, TypeScript tool code is inlined rather than loaded from files. Python tools can use filesystem imports normally.

Daytona Provider

The @jinba-toolbox/sandbox-daytona package implements the sandbox interface using the Daytona SDK.

Configuration

import { createDaytonaProvider } from "@jinba-toolbox/sandbox-daytona";

const provider = createDaytonaProvider({
  apiKey: process.env.DAYTONA_API_KEY!,
  apiUrl: "https://app.daytona.io/api",  // Default
  target: "us",                           // Region: "us" or "eu"
  timeoutMs: 300000,                      // Default: 5 minutes
});
OptionTypeDefaultDescription
apiKeystring(required)Daytona API key
apiUrlstringhttps://app.daytona.io/apiDaytona API URL
targetstringusTarget region (us or eu)
timeoutMsnumber300000Default timeout (5 minutes)

How It Works

  1. Sandbox creation: The provider initializes the Daytona SDK and creates a sandbox with the specified language.
  2. Package installation: If packages are specified, the provider runs pip install or npm install inside the sandbox.
  3. Code execution: Tool code is executed via Daytona’s process.codeRun API.
  4. Lifecycle: Sandboxes can be started, stopped, and deleted.

ToolSet Sandbox Configuration

When creating a toolset, you specify which sandbox provider and settings to use:
await client.createToolSet({
  slug: "data-tools",
  name: { default: "Data Tools" },
  description: { default: "Data processing tools" },
  sandbox: {
    provider: "e2b",          // or "daytona"
    language: "python",       // or "typescript"
    packages: [
      { name: "pandas", version: "2.0" },
      { name: "numpy" }
    ],
    resources: {
      cpu: "2",
      memory: "4Gi",
      timeout: 120000
    }
  }
});
The SandboxConfig stored on each toolset:
interface SandboxConfig {
  provider: "e2b" | "daytona";
  language: "python" | "typescript";
  runtime?: string;
  packages: { name: string; version?: string }[];
  resources: {
    cpu?: string;
    memory?: string;
    timeout?: number;
  };
}

Error Handling

All sandbox packages use a unified SandboxError type:
class SandboxError extends Error {
  constructor(
    message: string,
    code: SandboxErrorCode,
    cause?: unknown
  );
}

type SandboxErrorCode =
  | "CONNECTION_FAILED"    // Failed to connect to provider
  | "TIMEOUT"              // Execution timed out
  | "NOT_FOUND"            // Sandbox not found (resume)
  | "EXECUTION_FAILED"     // Code execution failed
  | "INTERNAL";            // Unexpected internal error

Adding a New Provider

The architecture is designed for extensibility. To add a new sandbox provider:
1

Create a new package

Create packages/sandbox-<provider> with the standard package structure.
2

Implement the SandboxProvider interface

Implement create() and resume() methods that return a SandboxInstance.
3

Register the provider

Add the provider case to the API server’s getSandboxProvider function. No changes to sandbox-core or business logic are needed.