@mosaic-sandbox/sdk
v0.14.7
Published
TypeScript SDK for Mosaic Sandbox
Readme
Mosaic Sandbox TypeScript SDK
Install:
npm install @mosaic-sandbox/sdkSet your endpoint and API token in the agent environment, then use the SDK:
import { Sandbox } from "@mosaic-sandbox/sdk";
const sandbox = await Sandbox.create({
template: "node-20",
endpoint: "https://sandbox.mosaicos.com",
apiToken: process.env.MOSAIC_API_TOKEN,
enableSsh: false
});
const child = await sandbox.process.start("npm run dev");
for await (const event of child.stream()) {
console.log(event.stdout);
}
const preview = await sandbox.preview(3000);
console.log(preview.url);
const checkpoint = await sandbox.snapshot("ready-to-test");
const fork = await sandbox.fork(checkpoint);
await fork.destroy();
await sandbox.destroy();Native SDK credentials use apiToken, not apiKey. Passing apiKey raises a
ConfigurationError before any request. If apiToken is omitted, the SDK checks
MOSAIC_API_TOKEN, then legacy MAR_API_TOKEN, then E2B_API_KEY only when it
contains a Mosaic key (msk_ prefix). Authenticated operations fail locally with
ConfigurationError when no credential is available; constructing a handle and
using local helpers does not require a token. Other unrecognized option keys
remain tolerated at runtime. E2B and Daytona compatibility shims continue to
accept their documented apiKey option.
SSH helpers are also available for agent-native setups:
const sandbox = await Sandbox.create({
template: "node-20",
endpoint: "https://sandbox.mosaicos.com",
apiToken: process.env.MOSAIC_API_TOKEN,
enableSsh: true
});
console.log(
sandbox.getSshConnectionString({
identityFile: "~/.ssh/mosaic_id_ed25519",
knownHostsFile: "~/.ssh/mosaic_known_hosts"
})
);
console.log(
sandbox.getCodexSshConfigEntry({
identityFile: "~/.ssh/mosaic_id_ed25519",
knownHostsFile: "~/.ssh/mosaic_known_hosts"
})
);Pass apiToken from the agent's secret store. Preview URLs are private bearer URLs,
expire automatically, and can be revoked with revokePreview().
Functions
A function is a name you keep for a one-shot run: a specification — template,
command, secrets, network policy, resources, timeout — that lives in your code.
Defining one makes no request and creates nothing to deploy or delete. Each
invocation creates one isolated microVM, runs the command, and destroys it, so
an idle function costs nothing. It is exported as FunctionSpec, because
Function is taken by the language.
import { FunctionSpec } from "@mosaic-sandbox/sdk";
const thumbnail = new FunctionSpec(["python", "-m", "thumbnail"], {
template: "python-3.11",
secrets: ["OBJECT_STORE_TOKEN"],
networkAllow: ["objects.mosaicos.com:443"],
timeoutMs: 60_000,
endpoint: "https://sandbox.mosaicos.com",
apiToken: process.env.MOSAIC_API_TOKEN
});
const result = await thumbnail.invoke({ env: { OBJECT_KEY: "images/input.jpg" } });
console.log(result.stdout, result.sandbox_destroyed);What the sandbox is stays fixed for every invocation. Only cwd, env,
stdin, timeoutMs and idempotencyKey may be passed per call; anything else
throws rather than being silently ignored, so one call cannot widen another's
secrets or egress. Retrying with the same idempotencyKey replays the first
invocation instead of running a second one.
Work that must outlive one synchronous call belongs in a process or job, and work that needs a URL belongs behind a preview.
Long-running attempts
Synchronous exec is capped at 900,000 ms (15 minutes). A durable process is owned by the sandbox rather than by the request that starts it, so persist the sandbox and process IDs and reconnect after a client restart. Its lifetime is bounded by its own timeout, when supplied, and by the sandbox TTL.
let sandbox = await Sandbox.create({ template: "base", ttlSeconds: 86_400 });
try {
const started = await sandbox.process.start(["python", "-m", "train"]);
const sandboxId = sandbox.id;
const processId = started.id;
sandbox = await Sandbox.connect(sandboxId);
const handle = (await sandbox.process.list()).find((p) => p.id === processId);
if (!handle) throw new Error("process not found");
for await (const chunk of handle.stream()) process.stdout.write(chunk.stdout);
const info = await handle.info();
if (info.exit_code !== 0) throw new Error(`attempt exited ${info.exit_code}`);
} finally {
await sandbox.destroy();
}A running process prevents hibernation; after it finishes, pause/resume works
normally. Call handle.kill() to cancel it. The full Python, TypeScript, Go,
CLI, and raw-HTTP recovery patterns are in the public documentation.
Vercel AI SDK tools
Give a model a sandbox without writing tools for it. This needs ai >= 4,
which you already have if you are using the AI SDK; the SDK imports it only
when you call this, and does not depend on it.
import { streamText } from "ai";
import { mosaicSandboxTools } from "@mosaic-sandbox/sdk/ai";
const sandbox = await mosaicSandboxTools({ template: "node-20" });
try {
const result = streamText({ model, prompt, tools: sandbox.tools });
} finally {
await sandbox.close();
}The tools — mosaic_run_command, mosaic_run_python, mosaic_run_javascript,
mosaic_read_file, mosaic_write_file, mosaic_list_files and
mosaic_start_server — share one sandbox, created on the first call rather
than when the agent is built. mosaic_start_server returns a public HTTPS
preview URL, so "run the dev server and show me" is a single tool call.
close() destroys the sandbox the toolset created but leaves a sandbox you
passed in alone, and the toolset is spent either way — a later tool call throws
rather than quietly starting a second machine.
toolDefinitions() exports the same set as plain JSON Schema, for frameworks
that take their tools that way.
