pi-extension-opensandbox
Server-side Pi extension factory that routes built-in tools to a remote OpenSandbox sandbox
Package details
Install pi-extension-opensandbox from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-extension-opensandbox- Package
pi-extension-opensandbox- Version
0.1.7- Published
- Sep 15, 2026
- Downloads
- 857/mo · 650/wk
- Author
- okliuxing
- License
- unknown
- Types
- extension
- Size
- 142.4 KB
- Dependencies
- 1 dependency · 2 peers
Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Pi OpenSandbox Extension
pi-extension-opensandbox is a server-side Pi extension factory. It takes an OpenSandbox configuration, runs that sandbox for the Pi session, and routes Pi's bash, read, write, edit, ls, find, and grep tools to it. The extension has no CLI flags and does not read environment variables.
Embed
Create a factory from a validated configuration and supply it as an inline extension:
import { createOpenSandboxExtension } from "pi-extension-opensandbox";
const extensionFactories = [{
name: "opensandbox",
factory: createOpenSandboxExtension({
config: sandboxConfig,
onSandboxEvent: async (event) => {
// event.phase is "ready" or "released"; the event carries the sandbox identity.
if (event.phase === "ready") trackSandbox(event.sandboxId);
},
}),
}];
Pass extensionFactories to createAgentSessionServices. Bind extensions before prompting and dispose the enclosing AgentSessionRuntime in a finally block. Disposal emits session_shutdown, where the lifecycle policy decides what happens to the sandbox.
Options:
config— sandbox configuration (see below).onSandboxEvent— called on every lifecycle phase:readywhen the sandbox is usable,releasedwhen the extension disposed of it (with the effectivemode). Events carrysandboxId,workspace,createdByExtension, andresumed.createSandbox— overrides the sandbox factory (defaults to the OpenSandbox client) so embedding services and tests can inject their own client.
Sandbox lifetime
One Pi session holds exactly one sandbox. The extension creates or connects it on session_start from config, routes the built-in tools and the interactive shell to it, renews its TTL while the session lives, and disposes of it on session_shutdown according to the lifecycle policy.
The sandbox is therefore not a named resource that an agent chooses between. Needing a second, differently-provisioned environment means starting a second Pi session: this factory is invoked once per session, so every session gets its own sandbox with its own configuration.
The extension attaches no meaning to what a sandbox is used for. Whatever an embedding service needs to identify a sandbox belongs in config.metadata, which is passed to OpenSandbox untouched; the extension only adds its own pi-extension and pi-session-id keys.
Failing to create the sandbox fails session_start. If the ready event callback throws, the sandbox is cleaned up (killed when the extension created it) and the session is left without a sandbox rather than half-connected.
Configuration
The factory config requires domain. protocol defaults to http; apiKey is optional and should be supplied by the embedding service, not an untrusted caller.
Creation requires exactly one source:
{
"domain": "sandbox.internal:8080",
"image": "opensandbox/code-interpreter:v1.1.0",
"workspace": "/workspace",
"volumes": [{
"name": "task-workspace",
"pvc": { "claimName": "task-workspace-pvc" },
"mountPath": "/workspace"
}]
}
Use snapshotId instead of image to create from a snapshot. A volume has exactly one pvc or ossfs backend and an absolute mount path. PVCs support createIfNotExists, deleteOnSandboxTermination, storageClass, storage, and accessModes. OSSFS supports bucket, endpoint, version, options, accessKeyId, and accessKeySecret.
To operate an existing sandbox, provide only sandboxId with connection settings:
{
"domain": "sandbox.internal:8080",
"sandboxId": "sbx_123",
"workspace": "/workspace"
}
Sandbox.connect() cannot add an image, snapshot, or mounts. The factory rejects those create-only fields for a connected sandbox. A paused sandbox is resumed before the agent begins, and a reused sandbox is renewed once before its renewal timer starts.
Lifecycle policy
lifecycle.onSessionShutdown decides what happens to a sandbox when the session shuts down:
| value | effect |
|---|---|
kill |
kill the sandbox (default) |
pause |
pause the sandbox; a later session resumes it by passing its sandboxId |
keep |
leave the sandbox running and only close the transport |
{
"domain": "sandbox.internal:8080",
"image": "opensandbox/code-interpreter:v1.1.0",
"lifecycle": { "onSessionShutdown": "keep" }
}
Only a sandbox the extension created is affected; a sandbox connected through sandboxId is only disconnected.
Safety
timeoutSeconds defaults to 3600 and must be a positive safe integer. The extension passes it to OpenSandbox without imposing a duration cap; the server's configured maximum still applies. The sandbox renews every half TTL or one hour, whichever is shorter, setting expiration to the renewal time plus timeoutSeconds. Renewal failures are logged and retried at the next interval. Requests never overlap, and shutdown stops renewal and awaits the pending request before pause, kill, or disconnect. Worker crashes stop renewal; this is not durable task recovery.
Remote-tool startup failures are fail-closed: no tool or interactive shell command falls back to the worker host. Metadata keys under opensandbox.io/ are rejected. Network egress is default-deny; networkAllowlist becomes explicit allow rules.
Development Checks
pnpm --dir packages/pi-extension-opensandbox check
Run the renewal regression tests from this package directory (mock timers and sandbox clients; no live services required):
node --import tsx --test scripts/renewal.test.mjs