SandboxesСправочник SDK

Справочник по Sandbox API и SDK

С Lizard SDK вы управляете Sandboxes в Lizard (lizard.build) из кода: @lizard-build/sdk в npm для TypeScript и JavaScript и lizard-sdk в PyPI для Python, который импортируется как lizard. Оба клиента вызывают один и тот же HTTP API. На этой странице перечислены все вызовы для sandbox с именами в TypeScript и Python и примерами на обоих языках. Для первого запуска по шагам используйте быстрый старт; что такое Sandboxes, описано в обзоре.

Установка и вход

npm install @lizard-build/sdk
pip install lizard-sdk
export LIZARD_API_KEY="<your-api-key>"

Создайте API-ключ в Settings → API keys или командой lizard keys create <name>. SDK читает LIZARD_API_KEY; чтобы использовать другой ключ, передайте apiKey (в Python: api_key). Каждому sandbox нужен проект: передайте projectId (в Python: project_id) или ID, slug либо имя через project.

Параметры создания

TypeScriptPython
projectId / projectproject_id / project
timeoutMstimeout_ms
regionregion
volumeName / volumeIdvolume_name / volume_id
sizesize
snapshotIdsnapshot_id
envsenvs
metadatametadata
lizardTokenlizard_token
apiKeyapi_key
import { Sandbox } from '@lizard-build/sdk';
 
const sandbox = await Sandbox.create('base', {
  projectId: 'proj_123',
  size: 'small',
  timeoutMs: 10 * 60 * 1000,
  envs: { MODE: 'test' },
  metadata: { task: 'report-42' },
});
from lizard import Sandbox
 
sandbox = Sandbox.create(
    "base",
    project_id="proj_123",
    size="small",
    timeout_ms=10 * 60 * 1000,
    envs={"MODE": "test"},
    metadata={"task": "report-42"},
)
  • Первым аргументом передайте шаблон из каталога в разделе runtime и шаблоны. Без него SDK запускает base.
  • size принимает 'small', 'medium' или 'large': 2 vCPU / 4 GB, 4 vCPU / 8 GB (по умолчанию) или 8 vCPU / 16 GB; см. размеры и цены. Для sandbox, восстановленного из снимка, size не действует: он работает с размером источника.
  • timeoutMs по умолчанию равен 300000, то есть пяти минутам. 0 отключает истечение срока. См. ресурсы и сроки жизни.
  • При необходимости укажите регион явно; наличие региона в каталоге не гарантирует свободные ресурсы. Не задавайте region, если подключаете том: sandbox запустится там, где находится том.
  • В metadata укажите свои метки; их возвращают getInfo() и list().
  • lizardToken записывает в sandbox API-ключ liz_, чтобы внутри работал lizard. Этот ключ может прочитать всё, что работает в sandbox, поэтому передавайте ключ с доступом только к рабочему пространству.

Методы

TypeScriptPythonЧто делает
Sandbox.create(template, options)Sandbox.create(template, ...)Создать в проекте из шаблона каталога.
Sandbox.connect(sandboxId)Sandbox.connect(sandbox_id)Подключиться к существующему sandbox без возобновления.
Sandbox.list({ projectId })Sandbox.list(project_id=...)Получить список доступных Sandboxes; проверяйте их статус.
sandbox.getInfo()sandbox.get_info()Получить статус, runtime и сведения.
sandbox.process.exec(cmd, opts)sandbox.process.exec_(cmd, ...)Выполнить shell-команду или массив argv; вернуть stdout, stderr и код завершения.
sandbox.process.list()sandbox.process.list()Получить список процессов, запущенных через exec.
sandbox.process.kill(pid, signal)sandbox.process.kill(pid, signal)Отправить сигнал процессу и его дочерним процессам; по умолчанию SIGTERM.
sandbox.fs.write(path, data)sandbox.fs.write(path, data)Записать текст или байты; создать родительские папки.
sandbox.fs.read(path)sandbox.fs.read(path)Прочитать текст UTF-8.
sandbox.fs.readBytes(path)sandbox.fs.read_bytes(path)Прочитать байты файла.
sandbox.fs.list(path)sandbox.fs.list(path)Получить список файлов и папок.
sandbox.fs.stat(path)sandbox.fs.stat(path)Вернуть тип, размер, права, владельца и время изменения.
sandbox.fs.makeDir(path)sandbox.fs.make_dir(path)Создать папку и недостающие родительские папки.
sandbox.fs.move(from, to)sandbox.fs.move(src, dst)Переместить файл или папку.
sandbox.fs.remove(path)sandbox.fs.remove(path)Удалить файл или папку.
sandbox.exposePort(port)sandbox.expose_port(port)Опубликовать порт; вернуть url, hostname и токен доступа.
sandbox.getHost(port)sandbox.get_host(port)Опубликовать порт и вернуть его имя хоста; для запросов нужен sandbox.accessToken.
sandbox.unexpose(port)sandbox.unexpose(port)Убрать опубликованный порт.
sandbox.setTimeout(ms)sandbox.set_timeout(ms)Задать оставшийся срок жизни, минимум 1000 мс.
sandbox.pause() / sandbox.resume()sandbox.pause() / sandbox.resume()Поставить паузу или возобновление в очередь и сразу вернуть ответ.
sandbox.waitForStatus(status)sandbox.wait_for_status(status)Дождаться paused или running; выбросить ошибку, если пауза не удалась.
sandbox.fork({ count })sandbox.fork(count=...)Скопировать работающий sandbox от 1 до 10 раз вместе с памятью и процессами.
sandbox.snapshot(name, { poolSize })sandbox.snapshot(name, pool_size=...)Сохранить память и файлы /workspace в приватный снимок.
Sandbox.waitForSnapshot(snapshotId)Sandbox.wait_for_snapshot(snapshot_id)Дождаться готовности снимка.
Sandbox.restore(snapshotId)Sandbox.restore(snapshot_id)Запустить новый sandbox из снимка.
Sandbox.deleteSnapshot(snapshotId)Sandbox.delete_snapshot(snapshot_id)Удалить снимок; sandbox, восстановленные из него, продолжают работать.
sandbox.kill()sandbox.kill()Остановить sandbox и отключить том.

Запуск команд

process.exec (в Python: exec_) запускает команду и ждёт её завершения. Передайте onStdout или onStderr (в Python: on_stdout, on_stderr), чтобы получать вывод, пока команда выполняется; вызов всё равно вернёт полный результат в конце. Передайте массив argv вместо строки, чтобы обойти оболочку: так безопасно передавать недоверенный текст как аргумент.

const result = await sandbox.process.exec('npm test', {
  workdir: '/app',
  envs: { NODE_ENV: 'test' },
  timeoutMs: 120_000,
  onStdout: (chunk) => process.stdout.write(chunk),
});
if (result.exitCode !== 0) throw new Error(result.stderr);
 
await sandbox.process.exec(['grep', '-rn', '--', userQuery, '/workspace']);
result = sandbox.process.exec_(
    "npm test",
    workdir="/app",
    envs={"NODE_ENV": "test"},
    timeout_ms=120_000,
    on_stdout=lambda chunk: print(chunk, end=""),
)
if result.exit_code != 0:
    raise RuntimeError(result.stderr)
 
sandbox.process.exec_(["grep", "-rn", "--", user_query, "/workspace"])

Результат команды содержит stdout, stderr и код завершения. Ненулевой код завершения возвращается в результате; проверяйте его сами. Сбои запроса выбрасывают исключение. Таймаут команды составляет от 1 до 600 секунд. process.exec принимает envs, workdir и user; команды выполняются от root, если не передать user. Параметр envs, заданный при создании, доступен каждой команде в sandbox на Firecracker; sandbox в виде pod его игнорирует, поэтому там передавайте переменные в каждой команде.

Файлы

fs.write записывает текст или байты, а fs.read читает текст. Для двоичных файлов записывайте байты и читайте их обратно через fs.readBytes (в Python: read_bytes). Используйте абсолютные пути. Если пути нет, выбрасывается NotFoundError.

await sandbox.fs.write('/app/data.csv', 'x,y\n1,2\n');
const text = await sandbox.fs.read('/app/data.csv');
const entries = await sandbox.fs.list('/app');
const png = await sandbox.fs.readBytes('/app/chart.png');
sandbox.fs.write("/app/data.csv", "x,y\n1,2\n")
text = sandbox.fs.read("/app/data.csv")
entries = sandbox.fs.list("/app")
png = sandbox.fs.read_bytes("/app/chart.png")

Файлы вне тома Persistent Volumes в /workspace пропадают после остановки sandbox.

Порты

Запустите сервер на 0.0.0.0 внутри sandbox, затем опубликуйте его порт. Каждому запросу нужен токен доступа: в заголовке X-Lizard-Access-Token или один раз как ?lizard_token= в браузере. См. URL предпросмотра.

const { url, hostname, accessToken } = await sandbox.exposePort(3000);
console.log(url); // open in a browser; it carries the token
await fetch(`https://${hostname}/api`, { headers: { 'X-Lizard-Access-Token': accessToken } });
await sandbox.unexpose(3000);
import httpx
 
exposed = sandbox.expose_port(3000)
print(exposed.url)  # open in a browser; it carries the token
httpx.get(f"https://{exposed.hostname}/api", headers={"X-Lizard-Access-Token": exposed.access_token})
sandbox.unexpose(3000)

Пауза, снимки и fork

Эти вызовы сохраняют память и запущенные процессы, а не только файлы. В быстром старте описано, что сохраняет каждый из них, как оплачиваются восстановленные sandbox и что может не сработать.

await sandbox.pause();
await sandbox.waitForStatus('paused');
await sandbox.resume();
await sandbox.waitForStatus('running');
 
const saved = await sandbox.snapshot('ready-env');
const copy = await Sandbox.restore(saved.id);
 
for (const f of await sandbox.fork({ count: 2 })) {
  if (f.sandbox) await f.sandbox.process.exec('echo hi');
  else console.warn('fork failed:', f.error);
}
sandbox.pause()
sandbox.wait_for_status("paused")
sandbox.resume()
sandbox.wait_for_status("running")
 
saved = sandbox.snapshot("ready-env")
copy = Sandbox.restore(saved["id"])
 
for f in sandbox.fork(count=2):
    if f.sandbox:
        f.sandbox.process.exec_("echo hi")
    else:
        print("fork failed:", f.error)

fork возвращает по одной записи на каждую копию: новый sandbox или ошибку, например из-за лимита sandbox в аккаунте. Каждая копия оплачивается так же, как источник.

Запуск кода с сохранением состояния

CodeSandbox запускает шаблон interpreter и выполняет код через runCode (в Python: run_code). В этом шаблоне Python работает в постоянном ядре Jupyter для каждого контекста, поэтому переменные сохраняются между вызовами, а результаты могут включать графики в виде image/png. JavaScript и bash при каждом вызове запускаются как новый процесс. См. интерпретатор кода.

Тома

Подключите том Persistent Volumes по имени, чтобы сохранять /workspace между sandbox:

import { Sandbox, Volume } from '@lizard-build/sdk';
 
await Volume.getOrCreate('proj_123', 'agent-workdir', { sizeGb: 10 });
const sandbox = await Sandbox.create('base', { projectId: 'proj_123', volumeName: 'agent-workdir' });
from lizard import Sandbox, Volume
 
Volume.get_or_create("proj_123", "agent-workdir", size_gb=10)
sandbox = Sandbox.create("base", project_id="proj_123", volume_name="agent-workdir")

Недоступные API

Наблюдение за файлами пока недоступно. sandbox.fork() возвращает 409, если подключён том, и 501 для sandbox, работающего как pod. Sandbox.connect не возобновляет приостановленный или остановленный sandbox; для приостановленного вызовите resume(). Доступа по протоколу SSH нет; lizard sandbox ssh открывает интерактивную оболочку с учётными данными CLI.

Ошибки

При сбоях API SDK выбрасывает эти ошибки (в Python: исключения). У каждой есть HTTP-статус и code, если API его передал.

ОшибкаЗначение
PaymentRequiredErrorHTTP 402: аккаунту нужно подключить Pro или оплатить счёт, прежде чем что-то создавать. В url указан адрес страницы, которую нужно открыть.
AuthenticationErrorHTTP 401 или 403: неверный ключ или нет доступа.
NotFoundErrorHTTP 404: sandbox, файл или снимок не существует либо его срок истёк.
ConflictErrorHTTP 409: конфликт состояния, например fork sandbox с томом.
TimeoutErrorHTTP 408 или 504.
LizardErrorДругие сбои API, включая неподдерживаемые операции.

Если одновременно работает слишком много sandbox или запусков слишком много, API возвращает HTTP 429; см. лимиты аккаунта.

HTTP API

SDK обращается к REST API Lizard по адресу https://lizard.build. Передавайте API-ключ как Authorization: Bearer liz_... или в заголовке X-API-Key.

ВызовЗапрос
СозданиеPOST /api/sandboxes с template, projectId, size, timeoutMs, envs, metadata, region, volumeName; возвращает 201 с sandboxId
СтатусGET /api/sandboxes/{id}
СписокGET /api/sandboxes или GET /api/projects/{projectId}/sandboxes для одного проекта
Запуск командыPOST /api/sandboxes/{id}/exec с cmd, envs, workdir, user, timeoutMs; возвращает stdout, stderr, exitCode
Изменение срока жизниPOST /api/sandboxes/{id}/timeout с timeoutMs
Пауза или возобновлениеPOST /api/sandboxes/{id}/pause, POST /api/sandboxes/{id}/resume
Публикация портаPOST /api/sandboxes/{id}/expose/{port}; DELETE по тому же пути убирает его
ОстановкаDELETE /api/sandboxes/{id}
curl -X POST https://lizard.build/api/sandboxes \
  -H "Authorization: Bearer $LIZARD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template": "base", "projectId": "proj_123", "timeoutMs": 300000}'

Прямой API создаёт sandbox без срока жизни, если не передать timeoutMs.

Хранение и оплата

Оплата, исходящий трафик и то, что сохраняется после остановки, описаны в разделе хранение и оплата. Размеры и почасовые цены смотрите в лимитах.

Обновлено